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.
- quasarr/api/captcha/__init__.py +452 -230
- quasarr/downloads/__init__.py +7 -0
- quasarr/downloads/linkcrypters/filecrypt.py +2 -36
- quasarr/downloads/packages/__init__.py +52 -38
- quasarr/downloads/sources/dl.py +24 -122
- quasarr/downloads/sources/wx.py +96 -54
- quasarr/providers/jd_cache.py +131 -0
- quasarr/providers/obfuscated.py +6 -6
- quasarr/providers/utils.py +177 -0
- quasarr/providers/version.py +1 -1
- quasarr/search/sources/wx.py +40 -24
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/METADATA +1 -3
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/RECORD +17 -16
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/WHEEL +0 -0
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/entry_points.txt +0 -0
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/licenses/LICENSE +0 -0
- {quasarr-1.29.0.dist-info → quasarr-1.31.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Quasarr
|
|
3
|
+
# Project by https://github.com/rix1337
|
|
4
|
+
|
|
5
|
+
from quasarr.providers.myjd_api import TokenExpiredException, RequestTimeoutException, MYJDException
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JDPackageCache:
|
|
9
|
+
"""
|
|
10
|
+
Caches JDownloader package/link queries within a single request.
|
|
11
|
+
|
|
12
|
+
IMPORTANT: This cache is ONLY valid for the duration of ONE get_packages()
|
|
13
|
+
or delete_package() call. JDownloader state can be modified at any time by
|
|
14
|
+
the user or third-party tools, so cached data must NEVER persist across
|
|
15
|
+
separate requests.
|
|
16
|
+
|
|
17
|
+
This reduces redundant API calls within a single operation where the same
|
|
18
|
+
data (e.g., linkgrabber_links) is needed multiple times.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
# Cache is created and discarded within a single function call
|
|
22
|
+
cache = JDPackageCache(device)
|
|
23
|
+
packages = cache.linkgrabber_packages # Fetches from API
|
|
24
|
+
packages = cache.linkgrabber_packages # Returns cached (same request)
|
|
25
|
+
# Cache goes out of scope and is garbage collected
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, device):
|
|
29
|
+
self._device = device
|
|
30
|
+
self._linkgrabber_packages = None
|
|
31
|
+
self._linkgrabber_links = None
|
|
32
|
+
self._downloader_packages = None
|
|
33
|
+
self._downloader_links = None
|
|
34
|
+
self._archive_package_uuids = None # Set of package UUIDs containing archives
|
|
35
|
+
self._is_collecting = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def linkgrabber_packages(self):
|
|
39
|
+
if self._linkgrabber_packages is None:
|
|
40
|
+
try:
|
|
41
|
+
self._linkgrabber_packages = self._device.linkgrabber.query_packages()
|
|
42
|
+
except (TokenExpiredException, RequestTimeoutException, MYJDException):
|
|
43
|
+
self._linkgrabber_packages = []
|
|
44
|
+
return self._linkgrabber_packages
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def linkgrabber_links(self):
|
|
48
|
+
if self._linkgrabber_links is None:
|
|
49
|
+
try:
|
|
50
|
+
self._linkgrabber_links = self._device.linkgrabber.query_links()
|
|
51
|
+
except (TokenExpiredException, RequestTimeoutException, MYJDException):
|
|
52
|
+
self._linkgrabber_links = []
|
|
53
|
+
return self._linkgrabber_links
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def downloader_packages(self):
|
|
57
|
+
if self._downloader_packages is None:
|
|
58
|
+
try:
|
|
59
|
+
self._downloader_packages = self._device.downloads.query_packages()
|
|
60
|
+
except (TokenExpiredException, RequestTimeoutException, MYJDException):
|
|
61
|
+
self._downloader_packages = []
|
|
62
|
+
return self._downloader_packages
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def downloader_links(self):
|
|
66
|
+
if self._downloader_links is None:
|
|
67
|
+
try:
|
|
68
|
+
self._downloader_links = self._device.downloads.query_links()
|
|
69
|
+
except (TokenExpiredException, RequestTimeoutException, MYJDException):
|
|
70
|
+
self._downloader_links = []
|
|
71
|
+
return self._downloader_links
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def is_collecting(self):
|
|
75
|
+
if self._is_collecting is None:
|
|
76
|
+
try:
|
|
77
|
+
self._is_collecting = self._device.linkgrabber.is_collecting()
|
|
78
|
+
except (TokenExpiredException, RequestTimeoutException, MYJDException):
|
|
79
|
+
self._is_collecting = False
|
|
80
|
+
return self._is_collecting
|
|
81
|
+
|
|
82
|
+
def get_archive_package_uuids(self, downloader_packages, downloader_links):
|
|
83
|
+
"""
|
|
84
|
+
Get set of package UUIDs that contain at least one archive file.
|
|
85
|
+
|
|
86
|
+
Two-phase detection:
|
|
87
|
+
1. Check extractionStatus in link data (free - catches in-progress/completed extractions)
|
|
88
|
+
2. Single API call for all remaining packages (catches pre-extraction archives)
|
|
89
|
+
|
|
90
|
+
This correctly handles:
|
|
91
|
+
- Mixed packages (archive + non-archive files)
|
|
92
|
+
- Archives before extraction starts
|
|
93
|
+
- Archives during/after extraction
|
|
94
|
+
"""
|
|
95
|
+
if self._archive_package_uuids is not None:
|
|
96
|
+
return self._archive_package_uuids
|
|
97
|
+
|
|
98
|
+
self._archive_package_uuids = set()
|
|
99
|
+
|
|
100
|
+
if not downloader_packages:
|
|
101
|
+
return self._archive_package_uuids
|
|
102
|
+
|
|
103
|
+
all_package_uuids = {p.get("uuid") for p in downloader_packages if p.get("uuid")}
|
|
104
|
+
|
|
105
|
+
# Phase 1: Check extractionStatus in already-fetched link data (free - no API call)
|
|
106
|
+
# This catches packages where extraction is in progress or completed
|
|
107
|
+
for link in downloader_links:
|
|
108
|
+
extraction_status = link.get("extractionStatus")
|
|
109
|
+
if extraction_status: # Any non-empty extraction status means it's an archive
|
|
110
|
+
pkg_uuid = link.get("packageUUID")
|
|
111
|
+
if pkg_uuid:
|
|
112
|
+
self._archive_package_uuids.add(pkg_uuid)
|
|
113
|
+
|
|
114
|
+
# Phase 2: Single API call for all unchecked packages
|
|
115
|
+
unchecked_package_uuids = list(all_package_uuids - self._archive_package_uuids)
|
|
116
|
+
|
|
117
|
+
if unchecked_package_uuids:
|
|
118
|
+
try:
|
|
119
|
+
# One API call for ALL unchecked packages
|
|
120
|
+
archive_infos = self._device.extraction.get_archive_info([], unchecked_package_uuids)
|
|
121
|
+
if archive_infos:
|
|
122
|
+
for archive_info in archive_infos:
|
|
123
|
+
if archive_info:
|
|
124
|
+
# Extract package UUID from response
|
|
125
|
+
pkg_uuid = archive_info.get("packageUUID")
|
|
126
|
+
if pkg_uuid:
|
|
127
|
+
self._archive_package_uuids.add(pkg_uuid)
|
|
128
|
+
except:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
return self._archive_package_uuids
|