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
@@ -5,24 +5,26 @@
5
5
  import html
6
6
  import re
7
7
  from datetime import datetime, timedelta
8
- from json import loads, dumps
8
+ from json import dumps, loads
9
9
  from urllib.parse import quote
10
10
 
11
11
  import requests
12
12
  from bs4 import BeautifulSoup
13
13
 
14
- from quasarr.providers.log import info, debug
14
+ from quasarr.providers.log import debug, info
15
15
 
16
16
 
17
17
  def _get_db(table_name):
18
18
  """Lazy import to avoid circular dependency."""
19
19
  from quasarr.storage.sqlite_database import DataBase
20
+
20
21
  return DataBase(table_name)
21
22
 
22
23
 
23
24
  def _get_config(section):
24
25
  """Lazy import to avoid circular dependency."""
25
26
  from quasarr.storage.config import Config
27
+
26
28
  return Config(section)
27
29
 
28
30
 
@@ -32,9 +34,11 @@ class TitleCleaner:
32
34
  if not title:
33
35
  return ""
34
36
  sanitized_title = html.unescape(title)
35
- sanitized_title = re.sub(r"[^a-zA-Z0-9äöüÄÖÜß&-']", ' ', sanitized_title).strip()
37
+ sanitized_title = re.sub(
38
+ r"[^a-zA-Z0-9äöüÄÖÜß&-']", " ", sanitized_title
39
+ ).strip()
36
40
  sanitized_title = sanitized_title.replace(" - ", "-")
37
- sanitized_title = re.sub(r'\s{2,}', ' ', sanitized_title)
41
+ sanitized_title = re.sub(r"\s{2,}", " ", sanitized_title)
38
42
  return sanitized_title
39
43
 
40
44
  @staticmethod
@@ -49,10 +53,18 @@ class TitleCleaner:
49
53
  extracted_title = title
50
54
 
51
55
  tags_to_remove = [
52
- r'[\.\s]UNRATED.*', r'[\.\s]Unrated.*', r'[\.\s]Uncut.*', r'[\.\s]UNCUT.*',
53
- r'[\.\s]Directors[\.\s]Cut.*', r'[\.\s]Final[\.\s]Cut.*', r'[\.\s]DC.*',
54
- r'[\.\s]REMASTERED.*', r'[\.\s]EXTENDED.*', r'[\.\s]Extended.*',
55
- r'[\.\s]Theatrical.*', r'[\.\s]THEATRICAL.*'
56
+ r"[\.\s]UNRATED.*",
57
+ r"[\.\s]Unrated.*",
58
+ r"[\.\s]Uncut.*",
59
+ r"[\.\s]UNCUT.*",
60
+ r"[\.\s]Directors[\.\s]Cut.*",
61
+ r"[\.\s]Final[\.\s]Cut.*",
62
+ r"[\.\s]DC.*",
63
+ r"[\.\s]REMASTERED.*",
64
+ r"[\.\s]EXTENDED.*",
65
+ r"[\.\s]Extended.*",
66
+ r"[\.\s]Theatrical.*",
67
+ r"[\.\s]THEATRICAL.*",
56
68
  ]
57
69
 
58
70
  clean_title = extracted_title
@@ -60,7 +72,7 @@ class TitleCleaner:
60
72
  clean_title = re.sub(tag, "", clean_title, flags=re.IGNORECASE)
61
73
 
62
74
  clean_title = clean_title.replace(".", " ").strip()
63
- clean_title = re.sub(r'\s+', ' ', clean_title)
75
+ clean_title = re.sub(r"\s+", " ", clean_title)
64
76
  clean_title = clean_title.replace(" ", "+")
65
77
 
66
78
  return clean_title
@@ -71,6 +83,7 @@ class TitleCleaner:
71
83
 
72
84
  class IMDbAPI:
73
85
  """Tier 1: api.imdbapi.dev - Primary, fast, comprehensive."""
86
+
74
87
  BASE_URL = "https://api.imdbapi.dev"
75
88
 
76
89
  @staticmethod
@@ -86,7 +99,9 @@ class IMDbAPI:
86
99
  @staticmethod
87
100
  def get_akas(imdb_id):
88
101
  try:
89
- response = requests.get(f"{IMDbAPI.BASE_URL}/titles/{imdb_id}/akas", timeout=30)
102
+ response = requests.get(
103
+ f"{IMDbAPI.BASE_URL}/titles/{imdb_id}/akas", timeout=30
104
+ )
90
105
  response.raise_for_status()
91
106
  return response.json().get("akas", [])
92
107
  except Exception as e:
@@ -96,7 +111,10 @@ class IMDbAPI:
96
111
  @staticmethod
97
112
  def search_titles(query):
98
113
  try:
99
- response = requests.get(f"{IMDbAPI.BASE_URL}/search/titles?query={quote(query)}&limit=5", timeout=30)
114
+ response = requests.get(
115
+ f"{IMDbAPI.BASE_URL}/search/titles?query={quote(query)}&limit=5",
116
+ timeout=30,
117
+ )
100
118
  response.raise_for_status()
101
119
  return response.json().get("titles", [])
102
120
  except Exception as e:
@@ -106,6 +124,7 @@ class IMDbAPI:
106
124
 
107
125
  class IMDbCDN:
108
126
  """Tier 2: v2.sg.media-imdb.com - Fast fallback for English data."""
127
+
109
128
  CDN_URL = "https://v2.sg.media-imdb.com/suggestion"
110
129
 
111
130
  @staticmethod
@@ -115,9 +134,9 @@ class IMDbCDN:
115
134
  return None
116
135
 
117
136
  headers = {
118
- 'Accept-Language': f'{language},en;q=0.9',
119
- 'User-Agent': user_agent,
120
- 'Accept': 'application/json'
137
+ "Accept-Language": f"{language},en;q=0.9",
138
+ "User-Agent": user_agent,
139
+ "Accept": "application/json",
121
140
  }
122
141
 
123
142
  first_char = imdb_id[0].lower()
@@ -141,7 +160,7 @@ class IMDbCDN:
141
160
 
142
161
  @staticmethod
143
162
  def get_poster(imdb_id, user_agent):
144
- data = IMDbCDN._get_cdn_data(imdb_id, 'en', user_agent)
163
+ data = IMDbCDN._get_cdn_data(imdb_id, "en", user_agent)
145
164
  if data:
146
165
  image_node = data.get("i")
147
166
  if image_node and "imageUrl" in image_node:
@@ -151,7 +170,7 @@ class IMDbCDN:
151
170
  @staticmethod
152
171
  def get_title(imdb_id, user_agent):
153
172
  """Returns the English title from CDN."""
154
- data = IMDbCDN._get_cdn_data(imdb_id, 'en', user_agent)
173
+ data = IMDbCDN._get_cdn_data(imdb_id, "en", user_agent)
155
174
  if data and "l" in data:
156
175
  return data["l"]
157
176
  return None
@@ -160,11 +179,12 @@ class IMDbCDN:
160
179
  def search_titles(query, ttype, language, user_agent):
161
180
  try:
162
181
  clean_query = quote(query.lower().replace(" ", "_"))
163
- if not clean_query: return []
182
+ if not clean_query:
183
+ return []
164
184
 
165
185
  headers = {
166
- 'Accept-Language': f'{language},en;q=0.9',
167
- 'User-Agent': user_agent
186
+ "Accept-Language": f"{language},en;q=0.9",
187
+ "User-Agent": user_agent,
168
188
  }
169
189
 
170
190
  first_char = clean_query[0]
@@ -177,15 +197,18 @@ class IMDbCDN:
177
197
  results = []
178
198
  if "d" in data:
179
199
  for item in data["d"]:
180
- results.append({
181
- 'id': item.get('id'),
182
- 'titleNameText': item.get('l'),
183
- 'titleReleaseText': item.get('y')
184
- })
200
+ results.append(
201
+ {
202
+ "id": item.get("id"),
203
+ "titleNameText": item.get("l"),
204
+ "titleReleaseText": item.get("y"),
205
+ }
206
+ )
185
207
  return results
186
208
 
187
209
  except Exception as e:
188
210
  from quasarr.providers.log import debug
211
+
189
212
  debug(f"IMDb CDN search failed: {e}")
190
213
 
191
214
  return []
@@ -193,11 +216,12 @@ class IMDbCDN:
193
216
 
194
217
  class IMDbFlareSolverr:
195
218
  """Tier 3: FlareSolverr - Robust fallback using browser automation."""
219
+
196
220
  WEB_URL = "https://www.imdb.com"
197
221
 
198
222
  @staticmethod
199
223
  def _request(url):
200
- flaresolverr_url = _get_config('FlareSolverr').get('url')
224
+ flaresolverr_url = _get_config("FlareSolverr").get("url")
201
225
  flaresolverr_skipped = _get_db("skip_flaresolverr").retrieve("skipped")
202
226
 
203
227
  if not flaresolverr_url or flaresolverr_skipped:
@@ -210,8 +234,12 @@ class IMDbFlareSolverr:
210
234
  "maxTimeout": 60000,
211
235
  }
212
236
 
213
- response = requests.post(flaresolverr_url, json=post_data, headers={"Content-Type": "application/json"},
214
- timeout=60)
237
+ response = requests.post(
238
+ flaresolverr_url,
239
+ json=post_data,
240
+ headers={"Content-Type": "application/json"},
241
+ timeout=60,
242
+ )
215
243
  if response.status_code == 200:
216
244
  json_response = response.json()
217
245
  if json_response.get("status") == "ok":
@@ -223,11 +251,13 @@ class IMDbFlareSolverr:
223
251
 
224
252
  @staticmethod
225
253
  def get_poster(imdb_id):
226
- html_content = IMDbFlareSolverr._request(f"{IMDbFlareSolverr.WEB_URL}/title/{imdb_id}/")
254
+ html_content = IMDbFlareSolverr._request(
255
+ f"{IMDbFlareSolverr.WEB_URL}/title/{imdb_id}/"
256
+ )
227
257
  if html_content:
228
258
  try:
229
259
  soup = BeautifulSoup(html_content, "html.parser")
230
- poster_div = soup.find('div', class_='ipc-poster')
260
+ poster_div = soup.find("div", class_="ipc-poster")
231
261
  if poster_div and poster_div.div and poster_div.div.img:
232
262
  poster_set = poster_div.div.img.get("srcset")
233
263
  if poster_set:
@@ -250,14 +280,14 @@ class IMDbFlareSolverr:
250
280
 
251
281
  # Map language codes to country names commonly used in IMDb AKAs
252
282
  country_map = {
253
- 'de': ['Germany', 'Austria', 'Switzerland', 'West Germany'],
254
- 'fr': ['France', 'Canada', 'Belgium'],
255
- 'es': ['Spain', 'Mexico', 'Argentina'],
256
- 'it': ['Italy'],
257
- 'pt': ['Portugal', 'Brazil'],
258
- 'ru': ['Russia', 'Soviet Union'],
259
- 'ja': ['Japan'],
260
- 'hi': ['India']
283
+ "de": ["Germany", "Austria", "Switzerland", "West Germany"],
284
+ "fr": ["France", "Canada", "Belgium"],
285
+ "es": ["Spain", "Mexico", "Argentina"],
286
+ "it": ["Italy"],
287
+ "pt": ["Portugal", "Brazil"],
288
+ "ru": ["Russia", "Soviet Union"],
289
+ "ja": ["Japan"],
290
+ "hi": ["India"],
261
291
  }
262
292
 
263
293
  target_countries = country_map.get(language, [])
@@ -267,17 +297,24 @@ class IMDbFlareSolverr:
267
297
  items = soup.find_all("li", class_="ipc-metadata-list__item")
268
298
 
269
299
  for item in items:
270
- label_span = item.find("span", class_="ipc-metadata-list-item__label")
300
+ label_span = item.find(
301
+ "span", class_="ipc-metadata-list-item__label"
302
+ )
271
303
  if not label_span:
272
304
  # Sometimes it's an anchor if it's a link
273
- label_span = item.find("a", class_="ipc-metadata-list-item__label")
305
+ label_span = item.find(
306
+ "a", class_="ipc-metadata-list-item__label"
307
+ )
274
308
 
275
309
  if label_span:
276
310
  country = label_span.get_text(strip=True)
277
311
  # Check if this country matches our target language
278
312
  if any(c in country for c in target_countries):
279
313
  # Found a matching country, get the title
280
- title_span = item.find("span", class_="ipc-metadata-list-item__list-content-item")
314
+ title_span = item.find(
315
+ "span",
316
+ class_="ipc-metadata-list-item__list-content-item",
317
+ )
281
318
  if title_span:
282
319
  return title_span.get_text(strip=True)
283
320
 
@@ -297,21 +334,27 @@ class IMDbFlareSolverr:
297
334
  props = soup.find("script", text=re.compile("props"))
298
335
  if props:
299
336
  details = loads(props.string)
300
- results = details['props']['pageProps']['titleResults']['results']
337
+ results = details["props"]["pageProps"]["titleResults"]["results"]
301
338
  mapped_results = []
302
339
  for result in results:
303
340
  try:
304
- mapped_results.append({
305
- 'id': result["listItem"]["titleId"],
306
- 'titleNameText': result["listItem"]["titleText"],
307
- 'titleReleaseText': result["listItem"].get("releaseYear")
308
- })
341
+ mapped_results.append(
342
+ {
343
+ "id": result["listItem"]["titleId"],
344
+ "titleNameText": result["listItem"]["titleText"],
345
+ "titleReleaseText": result["listItem"].get(
346
+ "releaseYear"
347
+ ),
348
+ }
349
+ )
309
350
  except KeyError:
310
- mapped_results.append({
311
- 'id': result.get('id'),
312
- 'titleNameText': result.get("titleNameText"),
313
- 'titleReleaseText': result.get("titleReleaseText")
314
- })
351
+ mapped_results.append(
352
+ {
353
+ "id": result.get("id"),
354
+ "titleNameText": result.get("titleNameText"),
355
+ "titleReleaseText": result.get("titleReleaseText"),
356
+ }
357
+ )
315
358
  return mapped_results
316
359
 
317
360
  results = []
@@ -322,11 +365,13 @@ class IMDbFlareSolverr:
322
365
  href = a_tag.get("href", "")
323
366
  id_match = re.search(r"(tt\d+)", href)
324
367
  if id_match:
325
- results.append({
326
- 'id': id_match.group(1),
327
- 'titleNameText': a_tag.get_text(strip=True),
328
- 'titleReleaseText': ""
329
- })
368
+ results.append(
369
+ {
370
+ "id": id_match.group(1),
371
+ "titleNameText": a_tag.get_text(strip=True),
372
+ "titleReleaseText": "",
373
+ }
374
+ )
330
375
  return results
331
376
 
332
377
  except Exception as e:
@@ -338,6 +383,7 @@ class IMDbFlareSolverr:
338
383
  # Main Functions (Chain of Responsibility)
339
384
  # =============================================================================
340
385
 
386
+
341
387
  def _update_cache(imdb_id, key, value, language=None):
342
388
  db = _get_db("imdb_metadata")
343
389
  try:
@@ -350,11 +396,13 @@ def _update_cache(imdb_id, key, value, language=None):
350
396
  "year": None,
351
397
  "poster_link": None,
352
398
  "localized": {},
353
- "ttl": 0
399
+ "ttl": 0,
354
400
  }
355
401
 
356
402
  if key == "localized" and language:
357
- if "localized" not in metadata or not isinstance(metadata["localized"], dict):
403
+ if "localized" not in metadata or not isinstance(
404
+ metadata["localized"], dict
405
+ ):
358
406
  metadata["localized"] = {}
359
407
  metadata["localized"][language] = value
360
408
  else:
@@ -391,18 +439,19 @@ def get_poster_link(shared_state, imdb_id):
391
439
  return None
392
440
 
393
441
 
394
- def get_localized_title(shared_state, imdb_id, language='de'):
442
+ def get_localized_title(shared_state, imdb_id, language="de"):
395
443
  # 0. Check Cache (via get_imdb_metadata)
396
444
  imdb_metadata = get_imdb_metadata(imdb_id)
397
445
  if imdb_metadata:
398
446
  localized = imdb_metadata.get("localized", {}).get(language)
399
- if localized: return localized
400
- if language == 'en' and imdb_metadata.get("title"):
447
+ if localized:
448
+ return localized
449
+ if language == "en" and imdb_metadata.get("title"):
401
450
  return imdb_metadata.get("title")
402
451
 
403
452
  user_agent = shared_state.values["user_agent"]
404
453
 
405
- if language == 'en':
454
+ if language == "en":
406
455
  title = IMDbCDN.get_title(imdb_id, user_agent)
407
456
  if title:
408
457
  sanitized_title = TitleCleaner.sanitize(title)
@@ -447,14 +496,16 @@ def get_imdb_metadata(imdb_id):
447
496
  "year": None,
448
497
  "poster_link": None,
449
498
  "localized": {},
450
- "ttl": 0
499
+ "ttl": 0,
451
500
  }
452
501
 
453
502
  # 1. Try API
454
503
  response_json = IMDbAPI.get_title(imdb_id)
455
504
 
456
505
  if response_json:
457
- imdb_metadata["title"] = TitleCleaner.sanitize(response_json.get("primaryTitle", ""))
506
+ imdb_metadata["title"] = TitleCleaner.sanitize(
507
+ response_json.get("primaryTitle", "")
508
+ )
458
509
  imdb_metadata["year"] = response_json.get("startYear")
459
510
 
460
511
  days = 7 if imdb_metadata.get("title") and imdb_metadata.get("year") else 1
@@ -468,9 +519,12 @@ def get_imdb_metadata(imdb_id):
468
519
  akas = IMDbAPI.get_akas(imdb_id)
469
520
  if akas:
470
521
  for aka in akas:
471
- if aka.get("language"): continue
522
+ if aka.get("language"):
523
+ continue
472
524
  if aka.get("country", {}).get("code", "").lower() == "de":
473
- imdb_metadata["localized"]["de"] = TitleCleaner.sanitize(aka.get("text"))
525
+ imdb_metadata["localized"]["de"] = TitleCleaner.sanitize(
526
+ aka.get("text")
527
+ )
474
528
  break
475
529
 
476
530
  db.update_store(imdb_id, dumps(imdb_metadata))
@@ -483,7 +537,7 @@ def get_imdb_metadata(imdb_id):
483
537
  # 2. Fallback: Try CDN for basic info (English title, Year, Poster)
484
538
  # We can't get localized titles from CDN, but we can get the rest.
485
539
  # We need a user agent, but this function doesn't receive shared_state.
486
- # We'll skip CDN fallback here to avoid circular deps or complexity,
540
+ # We'll skip CDN fallback here to avoid circular deps or complexity,
487
541
  # as get_poster_link and get_localized_title handle their own fallbacks.
488
542
  # But to populate the DB, we could try. For now, return empty/partial if API fails.
489
543
 
@@ -508,8 +562,9 @@ def get_imdb_id_from_title(shared_state, title, language="de"):
508
562
  cached_data = db.retrieve(title)
509
563
  if cached_data:
510
564
  data = loads(cached_data)
511
- if data.get("timestamp") and datetime.fromtimestamp(data["timestamp"]) > datetime.now() - timedelta(
512
- hours=48):
565
+ if data.get("timestamp") and datetime.fromtimestamp(
566
+ data["timestamp"]
567
+ ) > datetime.now() - timedelta(hours=48):
513
568
  return data.get("imdb_id")
514
569
  except Exception:
515
570
  pass
@@ -519,26 +574,31 @@ def get_imdb_id_from_title(shared_state, title, language="de"):
519
574
  # 1. Try API
520
575
  search_results = IMDbAPI.search_titles(title)
521
576
  if search_results:
522
- imdb_id = _match_result(shared_state, title, search_results, ttype_api, is_api=True)
577
+ imdb_id = _match_result(
578
+ shared_state, title, search_results, ttype_api, is_api=True
579
+ )
523
580
 
524
581
  # 2. Try CDN (Fallback)
525
582
  if not imdb_id:
526
583
  search_results = IMDbCDN.search_titles(title, ttype_web, language, user_agent)
527
584
  if search_results:
528
- imdb_id = _match_result(shared_state, title, search_results, ttype_api, is_api=False)
585
+ imdb_id = _match_result(
586
+ shared_state, title, search_results, ttype_api, is_api=False
587
+ )
529
588
 
530
589
  # 3. Try FlareSolverr (Last Resort)
531
590
  if not imdb_id:
532
591
  search_results = IMDbFlareSolverr.search_titles(title, ttype_web)
533
592
  if search_results:
534
- imdb_id = _match_result(shared_state, title, search_results, ttype_api, is_api=False)
593
+ imdb_id = _match_result(
594
+ shared_state, title, search_results, ttype_api, is_api=False
595
+ )
535
596
 
536
597
  # Update Cache
537
598
  try:
538
- db.update_store(title, dumps({
539
- "imdb_id": imdb_id,
540
- "timestamp": datetime.now().timestamp()
541
- }))
599
+ db.update_store(
600
+ title, dumps({"imdb_id": imdb_id, "timestamp": datetime.now().timestamp()})
601
+ )
542
602
  except Exception:
543
603
  pass
544
604
 
@@ -550,19 +610,28 @@ def get_imdb_id_from_title(shared_state, title, language="de"):
550
610
 
551
611
  def _match_result(shared_state, title, results, ttype_api, is_api=False):
552
612
  for result in results:
553
- found_title = result.get("primaryTitle") if is_api else result.get("titleNameText")
613
+ found_title = (
614
+ result.get("primaryTitle") if is_api else result.get("titleNameText")
615
+ )
554
616
  found_id = result.get("id")
555
617
 
556
618
  if is_api:
557
619
  found_type = result.get("type")
558
- if ttype_api == "TV_SERIES" and found_type not in ["tvSeries", "tvMiniSeries"]: continue
559
- if ttype_api == "MOVIE" and found_type not in ["movie", "tvMovie"]: continue
620
+ if ttype_api == "TV_SERIES" and found_type not in [
621
+ "tvSeries",
622
+ "tvMiniSeries",
623
+ ]:
624
+ continue
625
+ if ttype_api == "MOVIE" and found_type not in ["movie", "tvMovie"]:
626
+ continue
560
627
 
561
628
  if shared_state.search_string_in_sanitized_title(title, found_title):
562
629
  return found_id
563
630
 
564
631
  for result in results:
565
- found_title = result.get("primaryTitle") if is_api else result.get("titleNameText")
632
+ found_title = (
633
+ result.get("primaryTitle") if is_api else result.get("titleNameText")
634
+ )
566
635
  found_id = result.get("id")
567
636
  if shared_state.search_string_in_sanitized_title(title, found_title):
568
637
  return found_id