quasarr 1.29.0__py3-none-any.whl → 1.31.0__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.

@@ -6,9 +6,12 @@ import os
6
6
  import re
7
7
  import socket
8
8
  import sys
9
+ from concurrent.futures import ThreadPoolExecutor, as_completed
10
+ from io import BytesIO
9
11
  from urllib.parse import urlparse
10
12
 
11
13
  import requests
14
+ from PIL import Image
12
15
 
13
16
  # Fallback user agent when FlareSolverr is not available
14
17
  FALLBACK_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
@@ -187,3 +190,177 @@ def is_site_usable(shared_state, shorthand):
187
190
  password = config.get('password')
188
191
 
189
192
  return bool(user and password)
193
+
194
+
195
+ # =============================================================================
196
+ # LINK STATUS CHECKING
197
+ # =============================================================================
198
+
199
+ def generate_status_url(href, crypter_type):
200
+ """
201
+ Generate a status URL for crypters that support it.
202
+ Returns None if status URL cannot be generated.
203
+ """
204
+ if crypter_type == "hide":
205
+ # hide.cx links: https://hide.cx/folder/{UUID} or /container/{UUID} → https://hide.cx/state/{UUID}
206
+ match = re.search(r'hide\.cx/(?:folder/|container/)?([a-f0-9-]{36})', href, re.IGNORECASE)
207
+ if match:
208
+ uuid = match.group(1)
209
+ return f"https://hide.cx/state/{uuid}"
210
+
211
+ elif crypter_type == "tolink":
212
+ # tolink links: https://tolink.to/f/{ID} → https://tolink.to/f/{ID}/s/status.png
213
+ match = re.search(r'tolink\.to/f/([a-zA-Z0-9]+)', href, re.IGNORECASE)
214
+ if match:
215
+ link_id = match.group(1)
216
+ return f"https://tolink.to/f/{link_id}/s/status.png"
217
+
218
+ return None
219
+
220
+
221
+ def detect_crypter_type(url):
222
+ """Detect crypter type from URL for status checking."""
223
+ url_lower = url.lower()
224
+ if 'hide.' in url_lower:
225
+ return "hide"
226
+ elif 'tolink.' in url_lower:
227
+ return "tolink"
228
+ elif 'filecrypt.' in url_lower:
229
+ return "filecrypt"
230
+ elif 'keeplinks.' in url_lower:
231
+ return "keeplinks"
232
+ return None
233
+
234
+
235
+ def image_has_green(image_data):
236
+ """
237
+ Analyze image data to check if it contains green pixels.
238
+ Returns True if any significant green is detected (indicating online status).
239
+ """
240
+ try:
241
+ img = Image.open(BytesIO(image_data))
242
+ # Convert palette images with transparency to RGBA first to avoid warning
243
+ if img.mode == 'P' and 'transparency' in img.info:
244
+ img = img.convert('RGBA')
245
+ img = img.convert('RGB')
246
+
247
+ pixels = list(img.getdata())
248
+
249
+ for r, g, b in pixels:
250
+ # Check if pixel is greenish: green channel is dominant
251
+ # and has a reasonable absolute value
252
+ if g > 100 and g > r * 1.3 and g > b * 1.3:
253
+ return True
254
+
255
+ return False
256
+ except Exception:
257
+ # If we can't analyze, assume online to not skip valid links
258
+ return True
259
+
260
+
261
+ def fetch_status_image(status_url, shared_state=None):
262
+ """
263
+ Fetch a status image and return (status_url, image_data).
264
+ Returns (status_url, None) on failure.
265
+ """
266
+ try:
267
+ headers = {}
268
+ if shared_state:
269
+ user_agent = shared_state.values.get("user_agent")
270
+ if user_agent:
271
+ headers["User-Agent"] = user_agent
272
+ response = requests.get(status_url, headers=headers, timeout=10)
273
+ if response.status_code == 200:
274
+ return (status_url, response.content)
275
+ except Exception:
276
+ pass
277
+ return (status_url, None)
278
+
279
+
280
+ def check_links_online_status(links_with_status, shared_state=None):
281
+ """
282
+ Check online status for links that have status URLs.
283
+ Returns list of links that are online (or have no status URL to check).
284
+
285
+ links_with_status: list of [href, identifier, status_url] where status_url can be None
286
+ shared_state: optional shared state for user agent
287
+ """
288
+ links_to_check = [(i, link) for i, link in enumerate(links_with_status) if link[2]]
289
+
290
+ if not links_to_check:
291
+ # No status URLs to check, return all links as potentially online
292
+ return [[link[0], link[1]] for link in links_with_status]
293
+
294
+ # Batch fetch status images
295
+ status_results = {} # status_url -> has_green
296
+ status_urls = list(set(link[2] for _, link in links_to_check))
297
+
298
+ batch_size = 10
299
+ for i in range(0, len(status_urls), batch_size):
300
+ batch = status_urls[i:i + batch_size]
301
+ with ThreadPoolExecutor(max_workers=batch_size) as executor:
302
+ futures = [executor.submit(fetch_status_image, url, shared_state) for url in batch]
303
+ for future in as_completed(futures):
304
+ try:
305
+ status_url, image_data = future.result()
306
+ if image_data:
307
+ status_results[status_url] = image_has_green(image_data)
308
+ else:
309
+ # Could not fetch, assume online
310
+ status_results[status_url] = True
311
+ except Exception:
312
+ pass
313
+
314
+ # Filter to online links
315
+ online_links = []
316
+
317
+ for link in links_with_status:
318
+ href, identifier, status_url = link
319
+ if not status_url:
320
+ # No status URL, include link
321
+ online_links.append([href, identifier])
322
+ elif status_url in status_results:
323
+ if status_results[status_url]:
324
+ online_links.append([href, identifier])
325
+ else:
326
+ # Status check failed, include link
327
+ online_links.append([href, identifier])
328
+
329
+ return online_links
330
+
331
+
332
+ def filter_offline_links(links, shared_state=None, log_func=None):
333
+ """
334
+ Filter out offline links from a list of [url, identifier] pairs.
335
+ Only checks links where status can be verified (hide.cx, tolink).
336
+ Returns filtered list of [url, identifier] pairs.
337
+ """
338
+ if not links:
339
+ return links
340
+
341
+ # Build list with status URLs
342
+ links_with_status = []
343
+ for link in links:
344
+ url = link[0]
345
+ identifier = link[1] if len(link) > 1 else "unknown"
346
+ crypter_type = detect_crypter_type(url)
347
+ status_url = generate_status_url(url, crypter_type) if crypter_type else None
348
+ links_with_status.append([url, identifier, status_url])
349
+
350
+ # Check if any links can be verified
351
+ verifiable_count = sum(1 for l in links_with_status if l[2])
352
+ if verifiable_count == 0:
353
+ # Nothing to verify, return original links
354
+ return links
355
+
356
+ if log_func:
357
+ log_func(f"Checking online status for {verifiable_count} verifiable link(s)...")
358
+
359
+ # Check status and filter
360
+ online_links = check_links_online_status(links_with_status, shared_state)
361
+
362
+ if log_func and len(online_links) < len(links):
363
+ offline_count = len(links) - len(online_links)
364
+ log_func(f"Filtered out {offline_count} offline link(s)")
365
+
366
+ return online_links
@@ -8,7 +8,7 @@ import requests
8
8
 
9
9
 
10
10
  def get_version():
11
- return "1.29.0"
11
+ return "1.31.0"
12
12
 
13
13
 
14
14
  def get_latest_version():
@@ -131,6 +131,7 @@ def wx_feed(shared_state, start_time, request_from, mirror=None):
131
131
  def wx_search(shared_state, start_time, request_from, search_string, mirror=None, season=None, episode=None):
132
132
  """
133
133
  Search using internal API.
134
+ Deduplicates results by fulltitle - each unique release appears only once.
134
135
  """
135
136
  releases = []
136
137
  host = shared_state.values["config"]("Hostnames").get(hostname)
@@ -201,6 +202,9 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
201
202
 
202
203
  debug(f"{hostname.upper()}: Found {len(items)} items in search results")
203
204
 
205
+ # Track seen titles to deduplicate (mirrors have same fulltitle)
206
+ seen_titles = set()
207
+
204
208
  for item in items:
205
209
  try:
206
210
  uid = item.get('uid')
@@ -238,29 +242,34 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
238
242
  title = title.replace(' ', '.')
239
243
 
240
244
  if shared_state.is_valid_release(title, request_from, search_string, season, episode):
241
- published = detail_item.get('updated_at') or detail_item.get('created_at')
242
- if not published:
243
- published = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
244
- password = f"www.{host}"
245
-
246
- payload = urlsafe_b64encode(
247
- f"{title}|{source}|{mirror}|0|{password}|{item_imdb_id or ''}".encode("utf-8")
248
- ).decode("utf-8")
249
- link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
250
-
251
- releases.append({
252
- "details": {
253
- "title": title,
254
- "hostname": hostname,
255
- "imdb_id": item_imdb_id,
256
- "link": link,
257
- "mirror": mirror,
258
- "size": 0,
259
- "date": published,
260
- "source": source
261
- },
262
- "type": "protected"
263
- })
245
+ # Skip if we've already seen this exact title
246
+ if title in seen_titles:
247
+ debug(f"{hostname.upper()}: Skipping duplicate main title: {title}")
248
+ else:
249
+ seen_titles.add(title)
250
+ published = detail_item.get('updated_at') or detail_item.get('created_at')
251
+ if not published:
252
+ published = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
253
+ password = f"www.{host}"
254
+
255
+ payload = urlsafe_b64encode(
256
+ f"{title}|{source}|{mirror}|0|{password}|{item_imdb_id or ''}".encode("utf-8")
257
+ ).decode("utf-8")
258
+ link = f"{shared_state.values['internal_address']}/download/?payload={payload}"
259
+
260
+ releases.append({
261
+ "details": {
262
+ "title": title,
263
+ "hostname": hostname,
264
+ "imdb_id": item_imdb_id,
265
+ "link": link,
266
+ "mirror": mirror,
267
+ "size": 0,
268
+ "date": published,
269
+ "source": source
270
+ },
271
+ "type": "protected"
272
+ })
264
273
 
265
274
  if 'releases' in detail_item and isinstance(detail_item['releases'], list):
266
275
  debug(f"{hostname.upper()}: Found {len(detail_item['releases'])} releases for {uid}")
@@ -279,6 +288,13 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
279
288
  debug(f"{hostname.upper()}: ✗ Release filtered out: {release_title}")
280
289
  continue
281
290
 
291
+ # Skip if we've already seen this exact title (deduplication)
292
+ if release_title in seen_titles:
293
+ debug(f"{hostname.upper()}: Skipping duplicate release: {release_title}")
294
+ continue
295
+
296
+ seen_titles.add(release_title)
297
+
282
298
  release_uid = release.get('uid')
283
299
  if release_uid:
284
300
  release_source = f"https://{host}/detail/{uid}?release={release_uid}"
@@ -323,7 +339,7 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
323
339
  debug(f"{hostname.upper()}: {traceback.format_exc()}")
324
340
  continue
325
341
 
326
- debug(f"{hostname.upper()}: Returning {len(releases)} total releases")
342
+ debug(f"{hostname.upper()}: Returning {len(releases)} total releases (deduplicated)")
327
343
 
328
344
  except Exception as e:
329
345
  info(f"Error in {hostname.upper()} search: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quasarr
3
- Version: 1.29.0
3
+ Version: 1.31.0
4
4
  Summary: Quasarr connects JDownloader with Radarr, Sonarr and LazyLibrarian. It also decrypts links protected by CAPTCHAs.
5
5
  Home-page: https://github.com/rix1337/Quasarr
6
6
  Author: rix1337
@@ -45,8 +45,6 @@ Quasarr will confidently handle the rest. Some CAPTCHA types require [Tampermonk
45
45
 
46
46
  # Instructions
47
47
 
48
- # Instructions
49
-
50
48
  1. Set up and run [JDownloader 2](https://jdownloader.org/download/index)
51
49
  2. Configure the integrations below
52
50
  3. (Optional) Set up [FlareSolverr 3](https://github.com/FlareSolverr/FlareSolverr) for sites that require it
@@ -1,22 +1,22 @@
1
1
  quasarr/__init__.py,sha256=cEtxN2AuwKvrxpIvAR7UL997VtYQ4iN3Eo3ZnP-WjZQ,14682
2
2
  quasarr/api/__init__.py,sha256=UOyyuOjF2WN6Um2wwQNHjFA-Rj0prb11z8SCjbifKJU,6940
3
3
  quasarr/api/arr/__init__.py,sha256=BNEugX1hUF5kn8MIgdoyE1HeyabpojjxAa6RlXSem74,17518
4
- quasarr/api/captcha/__init__.py,sha256=IhJVn9iWtb01P2yfoqtOF7wSsiXizES7HNn29BX1uHk,60268
4
+ quasarr/api/captcha/__init__.py,sha256=2d7fTTo-FOXufsG_MxyaDPt8r1KVbXbSXnJAgrt8Qvo,72591
5
5
  quasarr/api/config/__init__.py,sha256=m0DrbanI0lK_PaZA6ey3osj5l1_tMjjYjoFKkzrdPu0,13692
6
6
  quasarr/api/sponsors_helper/__init__.py,sha256=kAZabPlplPYRG6Uw7ZHTk5uypualwvhs-NoTOjQhhhA,6369
7
7
  quasarr/api/statistics/__init__.py,sha256=NrBAjjHkIUE95HhPUGIfNqh2IqBqJ_zm00S90Y-Qnus,7038
8
- quasarr/downloads/__init__.py,sha256=bpNg6LNqoqpnA-U7uVDhq9jM6VYB2bkekCw1XxZRpWM,11613
8
+ quasarr/downloads/__init__.py,sha256=6COdDlJkNuWuJq7DLVqqb0fuH1b3HtsbvA0mlmtLeIs,11972
9
9
  quasarr/downloads/linkcrypters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  quasarr/downloads/linkcrypters/al.py,sha256=mfUG5VclC_-FcGoZL9zHYD7dz7X_YpaNmoKkgiyl9-0,8812
11
- quasarr/downloads/linkcrypters/filecrypt.py,sha256=GT51x_MG_hW4IpOF6OvL5r-2mTnMijI8K7_1D5Bfn4U,18884
11
+ quasarr/downloads/linkcrypters/filecrypt.py,sha256=He8b7HjoPA-LmRwVwY0l_5JAVlJ3sYOXs5tcWXooqI4,17055
12
12
  quasarr/downloads/linkcrypters/hide.py,sha256=8YmNm49JmVa1zZdTHpjK9gnQrX435Cq5fo4JTNsIpds,4850
13
- quasarr/downloads/packages/__init__.py,sha256=Cub3ztyFYBm30HprvZl7qvfYnjaOH9FsRWDLEyCPHkE,18305
13
+ quasarr/downloads/packages/__init__.py,sha256=6U4rk7hWoQCidIZy5O3-Pt5P7ZWEavHdbqZB0arxN1Y,18679
14
14
  quasarr/downloads/sources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  quasarr/downloads/sources/al.py,sha256=g587VESZRZHZ03uxHKpufEr5qAtzbyGLmoijksU35jk,27297
16
16
  quasarr/downloads/sources/by.py,sha256=kmUTn3izayRCV7W-t0E4kYE8qTbt3L3reCLozfvRGcU,3807
17
17
  quasarr/downloads/sources/dd.py,sha256=8X2tOle3qTq0b60Aa3o0uqp2vNELDHYYj99ERI7U_X0,2971
18
18
  quasarr/downloads/sources/dj.py,sha256=wY00hVRNhucZBG1hfExKqayhP1ISD8FFQm7wHYxutOk,404
19
- quasarr/downloads/sources/dl.py,sha256=P11c6P2Pxf8EMZdxBFcnY0y_Z35IAE1cJfTEx1Wkkw0,17995
19
+ quasarr/downloads/sources/dl.py,sha256=HF0kCzjY1elA6oVjIRlP37lLkAXStQTva2dRYLBTOE0,14823
20
20
  quasarr/downloads/sources/dt.py,sha256=80yIHAivsqoPKAaFdZ4wPFBVGCbHNUO130pv7EO2LTM,2605
21
21
  quasarr/downloads/sources/dw.py,sha256=_28-E58Hs9NVwHyLt2M1oYUxVZ-wpE5dQv8gMNhiAPM,2622
22
22
  quasarr/downloads/sources/he.py,sha256=AA6OrIkD3KS_w1ClvXyW1_9hujM6A8P_5VcMHRM6ngg,3680
@@ -27,20 +27,21 @@ quasarr/downloads/sources/sf.py,sha256=ecPHNsNiRNXTfQX9MBLzJKqrEc1IpkrKkBXpihTPh
27
27
  quasarr/downloads/sources/sj.py,sha256=Bkv0c14AXct50n_viaTNK3bYG-Bpvx8x2D0UN_6gm78,404
28
28
  quasarr/downloads/sources/sl.py,sha256=jWprFt1Hew1T67fB1O_pc9YWgc3NVh30KXSwSyS50Pc,3186
29
29
  quasarr/downloads/sources/wd.py,sha256=kr1I1uJa7ZkEPH2LA6alXTJEn0LBPgLCwIh3wLXwCv8,4447
30
- quasarr/downloads/sources/wx.py,sha256=EygMfkgBMZYj3tSk4gvj5DcojkRswGhY_y8FMPNnVeU,4834
30
+ quasarr/downloads/sources/wx.py,sha256=NzNNeqVL6sKkFKyreW-oerrreb5QP2tUGHTWHM5pMCU,7013
31
31
  quasarr/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  quasarr/providers/cloudflare.py,sha256=9iet8runc2VHVcA0_2z1qkrL6D5JKqz1ndktqCgsJFs,7873
33
33
  quasarr/providers/html_images.py,sha256=2n82gTJg7E7q2ytPFN4FWouYTIlmPYu_iHFtG7uktIA,28482
34
34
  quasarr/providers/html_templates.py,sha256=YMwdi7l_tHL0-qsUnwi4aPrE5Q6ZDxbjsPIfr-6uemY,10265
35
35
  quasarr/providers/imdb_metadata.py,sha256=10L4kZkt6Fg0HGdNcc6KCtIQHRYEqdarLyaMVN6mT8w,4843
36
+ quasarr/providers/jd_cache.py,sha256=TfoQWetKZA7vO_qj2wFOGhgmVSXiMhwGpjl7HtLc_-c,5372
36
37
  quasarr/providers/log.py,sha256=_g5RwtfuksARXnvryhsngzoJyFcNzj6suqd3ndqZM0Y,313
37
38
  quasarr/providers/myjd_api.py,sha256=Z3PEiO3c3UfDSr4Up5rgwTAnjloWHb-H1RkJ6BLKZv8,34140
38
39
  quasarr/providers/notifications.py,sha256=bohT-6yudmFnmZMc3BwCGX0n1HdzSVgQG_LDZm_38dI,4630
39
- quasarr/providers/obfuscated.py,sha256=xPI3WrteOiZN5BgNDp0CURcYfkRrdnRCz_cT7BpzIJU,1363310
40
+ quasarr/providers/obfuscated.py,sha256=8FcmY9cjvjOoth-6LrPZ1CZZbpKplcaMMBE06FkdYgA,1339187
40
41
  quasarr/providers/shared_state.py,sha256=-TIiH2lkCfovq7bzUZicpUjXEjS87ZHCcevsFgySOqw,29944
41
42
  quasarr/providers/statistics.py,sha256=cEQixYnDMDqtm5wWe40E_2ucyo4mD0n3SrfelhQi1L8,6452
42
- quasarr/providers/utils.py,sha256=Q5qjo0tP5DkrgIxXcM8jpm1-uSEswjaXlQ4VnzXwPAg,5741
43
- quasarr/providers/version.py,sha256=h5DOAeof2iTZmywTMg2cA15rO_4wlJXOsbvtxagN2JY,4004
43
+ quasarr/providers/utils.py,sha256=mcUPbcXMsLmrYv0CTZO5a9aOt2-JLyL3SZxu6N8OyjU,12075
44
+ quasarr/providers/version.py,sha256=bjolkJyZ2IG4sc5GvrvZfwTeYTHWNvMAMp2vgnpPVZ0,4004
44
45
  quasarr/providers/web_server.py,sha256=AYd0KRxdDWMBr87BP8wlSMuL4zZo0I_rY-vHBai6Pfg,1688
45
46
  quasarr/providers/sessions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
47
  quasarr/providers/sessions/al.py,sha256=WXue9LaT4y0BzsbKtHbN6bb_72c4AZZWR9NP-vg9-cg,12462
@@ -65,14 +66,14 @@ quasarr/search/sources/sf.py,sha256=3z_fvcafOh7U4D_vgq9yC8ktKeazI9fiAi96hCeXb5Q,
65
66
  quasarr/search/sources/sj.py,sha256=JRzoCDohClmGH7aXOz82KVUt6pZsZoBDBXvwvQrAijM,7074
66
67
  quasarr/search/sources/sl.py,sha256=5e5S7JvdbNOc2EthyOkfC4aTpG8O7fn4WS2O3_EXjnM,9463
67
68
  quasarr/search/sources/wd.py,sha256=O02j3irSlVw2qES82g_qHuavAk-njjSRH1dHSCnOUas,7540
68
- quasarr/search/sources/wx.py,sha256=_h1M6GhkJzixwHscrt0lMOnPSEDP1Xl24OypEe8Jy7c,12906
69
+ quasarr/search/sources/wx.py,sha256=zlRvg7Ls-DFRo4sUBMRAXZRMfE2mnaXCkzP7pu53pIY,13842
69
70
  quasarr/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
71
  quasarr/storage/config.py,sha256=SSTgIce2FVYoVTK_6OCU3msknhxuLA3EC4Kcrrf_dxQ,6378
71
72
  quasarr/storage/setup.py,sha256=0Gm6sHLmlcKvOGeli9eVuRVEP0Slz8-K5jZG0cNXaew,42041
72
73
  quasarr/storage/sqlite_database.py,sha256=yMqFQfKf0k7YS-6Z3_7pj4z1GwWSXJ8uvF4IydXsuTE,3554
73
- quasarr-1.29.0.dist-info/licenses/LICENSE,sha256=QQFCAfDgt7lSA8oSWDHIZ9aTjFbZaBJdjnGOHkuhK7k,1060
74
- quasarr-1.29.0.dist-info/METADATA,sha256=megStkH1cFUlcHPsB6gx3bJac5Mxy7no_x4Caeqptng,11025
75
- quasarr-1.29.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
76
- quasarr-1.29.0.dist-info/entry_points.txt,sha256=gXi8mUKsIqKVvn-bOc8E5f04sK_KoMCC-ty6b2Hf-jc,40
77
- quasarr-1.29.0.dist-info/top_level.txt,sha256=dipJdaRda5ruTZkoGfZU60bY4l9dtPlmOWwxK_oGSF0,8
78
- quasarr-1.29.0.dist-info/RECORD,,
74
+ quasarr-1.31.0.dist-info/licenses/LICENSE,sha256=QQFCAfDgt7lSA8oSWDHIZ9aTjFbZaBJdjnGOHkuhK7k,1060
75
+ quasarr-1.31.0.dist-info/METADATA,sha256=4jot6IFM_BIQwf8Ftx74l9W26M-2EaK8LXxC9XUn3qE,11009
76
+ quasarr-1.31.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
+ quasarr-1.31.0.dist-info/entry_points.txt,sha256=gXi8mUKsIqKVvn-bOc8E5f04sK_KoMCC-ty6b2Hf-jc,40
78
+ quasarr-1.31.0.dist-info/top_level.txt,sha256=dipJdaRda5ruTZkoGfZU60bY4l9dtPlmOWwxK_oGSF0,8
79
+ quasarr-1.31.0.dist-info/RECORD,,