quasarr 2.1.5__py3-none-any.whl → 2.2.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/__init__.py +94 -23
- quasarr/api/captcha/__init__.py +0 -12
- quasarr/api/config/__init__.py +22 -11
- quasarr/api/packages/__init__.py +26 -34
- quasarr/api/statistics/__init__.py +15 -15
- quasarr/downloads/__init__.py +9 -1
- quasarr/downloads/packages/__init__.py +2 -2
- quasarr/downloads/sources/al.py +6 -0
- quasarr/downloads/sources/by.py +29 -20
- quasarr/downloads/sources/dd.py +9 -1
- quasarr/downloads/sources/dl.py +3 -0
- quasarr/downloads/sources/dt.py +16 -7
- quasarr/downloads/sources/dw.py +22 -17
- quasarr/downloads/sources/he.py +11 -6
- quasarr/downloads/sources/mb.py +9 -3
- quasarr/downloads/sources/nk.py +9 -3
- quasarr/downloads/sources/nx.py +21 -17
- quasarr/downloads/sources/sf.py +21 -13
- quasarr/downloads/sources/sl.py +10 -2
- quasarr/downloads/sources/wd.py +18 -9
- quasarr/downloads/sources/wx.py +7 -11
- quasarr/providers/auth.py +1 -1
- quasarr/providers/cloudflare.py +1 -1
- quasarr/providers/hostname_issues.py +63 -0
- quasarr/providers/html_images.py +1 -18
- quasarr/providers/html_templates.py +104 -12
- quasarr/providers/obfuscated.py +11 -11
- quasarr/providers/sessions/al.py +27 -11
- quasarr/providers/sessions/dd.py +12 -4
- quasarr/providers/sessions/dl.py +19 -11
- quasarr/providers/sessions/nx.py +12 -4
- quasarr/providers/version.py +1 -1
- quasarr/search/sources/al.py +12 -1
- quasarr/search/sources/by.py +15 -4
- quasarr/search/sources/dd.py +22 -3
- quasarr/search/sources/dj.py +12 -1
- quasarr/search/sources/dl.py +12 -6
- quasarr/search/sources/dt.py +17 -4
- quasarr/search/sources/dw.py +15 -4
- quasarr/search/sources/fx.py +19 -6
- quasarr/search/sources/he.py +15 -2
- quasarr/search/sources/mb.py +15 -4
- quasarr/search/sources/nk.py +15 -2
- quasarr/search/sources/nx.py +15 -4
- quasarr/search/sources/sf.py +25 -8
- quasarr/search/sources/sj.py +14 -1
- quasarr/search/sources/sl.py +17 -2
- quasarr/search/sources/wd.py +15 -4
- quasarr/search/sources/wx.py +16 -18
- quasarr/storage/setup.py +150 -35
- {quasarr-2.1.5.dist-info → quasarr-2.2.0.dist-info}/METADATA +6 -3
- quasarr-2.2.0.dist-info/RECORD +82 -0
- {quasarr-2.1.5.dist-info → quasarr-2.2.0.dist-info}/WHEEL +1 -1
- quasarr-2.1.5.dist-info/RECORD +0 -81
- {quasarr-2.1.5.dist-info → quasarr-2.2.0.dist-info}/entry_points.txt +0 -0
- {quasarr-2.1.5.dist-info → quasarr-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {quasarr-2.1.5.dist-info → quasarr-2.2.0.dist-info}/top_level.txt +0 -0
quasarr/search/sources/sl.py
CHANGED
|
@@ -14,6 +14,7 @@ from urllib.parse import quote_plus
|
|
|
14
14
|
import requests
|
|
15
15
|
from bs4 import BeautifulSoup
|
|
16
16
|
|
|
17
|
+
from quasarr.providers.hostname_issues import mark_hostname_issue, clear_hostname_issue
|
|
17
18
|
from quasarr.providers.imdb_metadata import get_localized_title
|
|
18
19
|
from quasarr.providers.log import info, debug
|
|
19
20
|
|
|
@@ -60,8 +61,9 @@ def sl_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
60
61
|
headers = {'User-Agent': shared_state.values['user_agent']}
|
|
61
62
|
|
|
62
63
|
try:
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
r = requests.get(url, headers=headers, timeout=30)
|
|
65
|
+
r.raise_for_status()
|
|
66
|
+
root = ET.fromstring(r.text)
|
|
65
67
|
|
|
66
68
|
for item in root.find('channel').findall('item'):
|
|
67
69
|
try:
|
|
@@ -110,13 +112,18 @@ def sl_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
110
112
|
|
|
111
113
|
except Exception as e:
|
|
112
114
|
info(f"Error parsing {hostname.upper()} feed item: {e}")
|
|
115
|
+
mark_hostname_issue(hostname, "feed", str(e) if "e" in dir() else "Error occurred")
|
|
113
116
|
continue
|
|
114
117
|
|
|
115
118
|
except Exception as e:
|
|
116
119
|
info(f"Error loading {hostname.upper()} feed: {e}")
|
|
120
|
+
mark_hostname_issue(hostname, "feed", str(e) if "e" in dir() else "Error occurred")
|
|
117
121
|
|
|
118
122
|
elapsed = time.time() - start_time
|
|
119
123
|
debug(f"Time taken: {elapsed:.2f}s ({hostname})")
|
|
124
|
+
|
|
125
|
+
if releases:
|
|
126
|
+
clear_hostname_issue(hostname)
|
|
120
127
|
return releases
|
|
121
128
|
|
|
122
129
|
|
|
@@ -162,6 +169,7 @@ def sl_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
162
169
|
return r.text
|
|
163
170
|
except Exception as e:
|
|
164
171
|
info(f"Error fetching {hostname} url {url}: {e}")
|
|
172
|
+
mark_hostname_issue(hostname, "search", str(e) if "e" in dir() else "Error occurred")
|
|
165
173
|
return ''
|
|
166
174
|
|
|
167
175
|
html_texts = []
|
|
@@ -172,6 +180,7 @@ def sl_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
172
180
|
html_texts.append(future.result())
|
|
173
181
|
except Exception as e:
|
|
174
182
|
info(f"Error fetching {hostname} search page: {e}")
|
|
183
|
+
mark_hostname_issue(hostname, "search", str(e) if "e" in dir() else "Error occurred")
|
|
175
184
|
|
|
176
185
|
# Parse each result and collect unique releases (dedupe by source link)
|
|
177
186
|
seen_sources = set()
|
|
@@ -233,14 +242,20 @@ def sl_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
233
242
|
})
|
|
234
243
|
except Exception as e:
|
|
235
244
|
info(f"Error parsing {hostname.upper()} search item: {e}")
|
|
245
|
+
mark_hostname_issue(hostname, "search", str(e) if "e" in dir() else "Error occurred")
|
|
236
246
|
continue
|
|
237
247
|
except Exception as e:
|
|
238
248
|
info(f"Error parsing {hostname.upper()} search HTML: {e}")
|
|
249
|
+
mark_hostname_issue(hostname, "search", str(e) if "e" in dir() else "Error occurred")
|
|
239
250
|
continue
|
|
240
251
|
|
|
241
252
|
except Exception as e:
|
|
242
253
|
info(f"Error loading {hostname.upper()} search page: {e}")
|
|
254
|
+
mark_hostname_issue(hostname, "search", str(e) if "e" in dir() else "Error occurred")
|
|
243
255
|
|
|
244
256
|
elapsed = time.time() - start_time
|
|
245
257
|
debug(f"Search time: {elapsed:.2f}s ({hostname})")
|
|
258
|
+
|
|
259
|
+
if releases:
|
|
260
|
+
clear_hostname_issue(hostname)
|
|
246
261
|
return releases
|
quasarr/search/sources/wd.py
CHANGED
|
@@ -12,6 +12,7 @@ from urllib.parse import quote, quote_plus
|
|
|
12
12
|
import requests
|
|
13
13
|
from bs4 import BeautifulSoup
|
|
14
14
|
|
|
15
|
+
from quasarr.providers.hostname_issues import mark_hostname_issue, clear_hostname_issue
|
|
15
16
|
from quasarr.providers.imdb_metadata import get_localized_title
|
|
16
17
|
from quasarr.providers.log import info, debug
|
|
17
18
|
|
|
@@ -165,13 +166,18 @@ def wd_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
165
166
|
url = f"https://{wd}/{feed_type}"
|
|
166
167
|
headers = {'User-Agent': shared_state.values["user_agent"]}
|
|
167
168
|
try:
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
r = requests.get(url, headers=headers, timeout=10)
|
|
170
|
+
r.raise_for_status()
|
|
171
|
+
soup = BeautifulSoup(r.content, "html.parser")
|
|
170
172
|
releases = _parse_rows(soup, shared_state, wd, password, mirror)
|
|
171
173
|
except Exception as e:
|
|
172
174
|
info(f"Error loading {hostname.upper()} feed: {e}")
|
|
175
|
+
mark_hostname_issue(hostname, "feed", str(e) if "e" in dir() else "Error occurred")
|
|
173
176
|
releases = []
|
|
174
177
|
debug(f"Time taken: {time.time() - start_time:.2f}s ({hostname})")
|
|
178
|
+
|
|
179
|
+
if releases:
|
|
180
|
+
clear_hostname_issue(hostname)
|
|
175
181
|
return releases
|
|
176
182
|
|
|
177
183
|
|
|
@@ -193,8 +199,9 @@ def wd_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
193
199
|
headers = {'User-Agent': shared_state.values["user_agent"]}
|
|
194
200
|
|
|
195
201
|
try:
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
r = requests.get(url, headers=headers, timeout=10)
|
|
203
|
+
r.raise_for_status()
|
|
204
|
+
soup = BeautifulSoup(r.content, "html.parser")
|
|
198
205
|
releases = _parse_rows(
|
|
199
206
|
soup, shared_state, wd, password, mirror,
|
|
200
207
|
request_from=request_from,
|
|
@@ -203,6 +210,10 @@ def wd_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
203
210
|
)
|
|
204
211
|
except Exception as e:
|
|
205
212
|
info(f"Error loading {hostname.upper()} search: {e}")
|
|
213
|
+
mark_hostname_issue(hostname, "search", str(e) if "e" in dir() else "Error occurred")
|
|
206
214
|
releases = []
|
|
207
215
|
debug(f"Time taken: {time.time() - start_time:.2f}s ({hostname})")
|
|
216
|
+
|
|
217
|
+
if releases:
|
|
218
|
+
clear_hostname_issue(hostname)
|
|
208
219
|
return releases
|
quasarr/search/sources/wx.py
CHANGED
|
@@ -13,6 +13,7 @@ import requests
|
|
|
13
13
|
from bs4 import BeautifulSoup
|
|
14
14
|
from bs4 import XMLParsedAsHTMLWarning
|
|
15
15
|
|
|
16
|
+
from quasarr.providers.hostname_issues import mark_hostname_issue, clear_hostname_issue
|
|
16
17
|
from quasarr.providers.imdb_metadata import get_localized_title
|
|
17
18
|
from quasarr.providers.log import info, debug
|
|
18
19
|
|
|
@@ -39,13 +40,10 @@ def wx_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
try:
|
|
42
|
-
|
|
43
|
+
r = requests.get(rss_url, headers=headers, timeout=10)
|
|
44
|
+
r.raise_for_status()
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
info(f"{hostname.upper()}: RSS feed returned status {response.status_code}")
|
|
46
|
-
return releases
|
|
47
|
-
|
|
48
|
-
soup = BeautifulSoup(response.content, 'html.parser')
|
|
46
|
+
soup = BeautifulSoup(r.content, 'html.parser')
|
|
49
47
|
items = soup.find_all('entry')
|
|
50
48
|
|
|
51
49
|
if not items:
|
|
@@ -120,11 +118,14 @@ def wx_feed(shared_state, start_time, request_from, mirror=None):
|
|
|
120
118
|
|
|
121
119
|
except Exception as e:
|
|
122
120
|
info(f"Error loading {hostname.upper()} feed: {e}")
|
|
121
|
+
mark_hostname_issue(hostname, "feed", str(e) if "e" in dir() else "Error occurred")
|
|
123
122
|
return releases
|
|
124
123
|
|
|
125
124
|
elapsed_time = time.time() - start_time
|
|
126
125
|
debug(f"Time taken: {elapsed_time:.2f}s ({hostname})")
|
|
127
126
|
|
|
127
|
+
if releases:
|
|
128
|
+
clear_hostname_issue(hostname)
|
|
128
129
|
return releases
|
|
129
130
|
|
|
130
131
|
|
|
@@ -183,13 +184,10 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
183
184
|
debug(f"{hostname.upper()}: Searching: '{search_string}'")
|
|
184
185
|
|
|
185
186
|
try:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if response.status_code != 200:
|
|
189
|
-
debug(f"{hostname.upper()}: Search API returned status {response.status_code}")
|
|
190
|
-
return releases
|
|
187
|
+
r = requests.get(api_url, headers=headers, params=params, timeout=10)
|
|
188
|
+
r.raise_for_status()
|
|
191
189
|
|
|
192
|
-
data =
|
|
190
|
+
data = r.json()
|
|
193
191
|
|
|
194
192
|
if 'items' in data and 'data' in data['items']:
|
|
195
193
|
items = data['items']['data']
|
|
@@ -215,13 +213,10 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
215
213
|
debug(f"{hostname.upper()}: Fetching details for UID: {uid}")
|
|
216
214
|
|
|
217
215
|
detail_url = f'https://api.{host}/start/d/{uid}'
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if detail_response.status_code != 200:
|
|
221
|
-
debug(f"{hostname.upper()}: Detail API returned {detail_response.status_code} for {uid}")
|
|
222
|
-
continue
|
|
216
|
+
detail_r = requests.get(detail_url, headers=headers, timeout=10)
|
|
217
|
+
detail_r.raise_for_status()
|
|
223
218
|
|
|
224
|
-
detail_data =
|
|
219
|
+
detail_data = detail_r.json()
|
|
225
220
|
|
|
226
221
|
if 'item' in detail_data:
|
|
227
222
|
detail_item = detail_data['item']
|
|
@@ -344,6 +339,7 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
344
339
|
|
|
345
340
|
except Exception as e:
|
|
346
341
|
info(f"Error in {hostname.upper()} search: {e}")
|
|
342
|
+
mark_hostname_issue(hostname, "search", str(e) if "e" in dir() else "Error occurred")
|
|
347
343
|
|
|
348
344
|
debug(f"{hostname.upper()}: {traceback.format_exc()}")
|
|
349
345
|
return releases
|
|
@@ -351,4 +347,6 @@ def wx_search(shared_state, start_time, request_from, search_string, mirror=None
|
|
|
351
347
|
elapsed_time = time.time() - start_time
|
|
352
348
|
debug(f"Time taken: {elapsed_time:.2f}s ({hostname})")
|
|
353
349
|
|
|
350
|
+
if releases:
|
|
351
|
+
clear_hostname_issue(hostname)
|
|
354
352
|
return releases
|
quasarr/storage/setup.py
CHANGED
|
@@ -16,6 +16,7 @@ import quasarr.providers.sessions.dd
|
|
|
16
16
|
import quasarr.providers.sessions.dl
|
|
17
17
|
import quasarr.providers.sessions.nx
|
|
18
18
|
from quasarr.providers.auth import add_auth_routes, add_auth_hook
|
|
19
|
+
from quasarr.providers.hostname_issues import get_all_hostname_issues
|
|
19
20
|
from quasarr.providers.html_templates import render_button, render_form, render_success, render_fail, \
|
|
20
21
|
render_centered_html
|
|
21
22
|
from quasarr.providers.log import info
|
|
@@ -74,7 +75,7 @@ def render_reconnect_success(message, countdown_seconds=3):
|
|
|
74
75
|
'''
|
|
75
76
|
|
|
76
77
|
content = f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
77
|
-
<h2
|
|
78
|
+
<h2>✅ Success</h2>
|
|
78
79
|
<p>{message}</p>
|
|
79
80
|
{button_html}
|
|
80
81
|
{script}
|
|
@@ -156,9 +157,20 @@ def path_config(shared_state):
|
|
|
156
157
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
157
158
|
|
|
158
159
|
|
|
160
|
+
def _escape_js_for_html_attr(s):
|
|
161
|
+
"""Escape a string for use inside a JS string literal within an HTML attribute."""
|
|
162
|
+
if s is None:
|
|
163
|
+
return ""
|
|
164
|
+
return str(s).replace("\\", "\\\\").replace("'", "\\'").replace('"', '"').replace("\n", "\\n").replace("\r", "")
|
|
165
|
+
|
|
166
|
+
|
|
159
167
|
def hostname_form_html(shared_state, message, show_restart_button=False, show_skip_management=False):
|
|
160
168
|
hostname_fields = '''
|
|
161
|
-
<label for="{id}"
|
|
169
|
+
<label for="{id}" onclick="showStatusDetail(\'{id}\', \'{label}\', \'{status}\', \'{error_details_for_modal}\', \'{timestamp}\', \'{operation}\', \'{url}\')"
|
|
170
|
+
style="cursor:pointer; display:inline-flex; align-items:center; gap:4px;" title="{status_title}">
|
|
171
|
+
<span class="status-indicator" id="status-{id}" data-status="{status}">{status_emoji}</span>
|
|
172
|
+
{label}
|
|
173
|
+
</label>
|
|
162
174
|
<input type="text" id="{id}" name="{id}" placeholder="example.com" autocorrect="off" autocomplete="off" value="{value}"><br>
|
|
163
175
|
'''
|
|
164
176
|
|
|
@@ -172,28 +184,58 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
172
184
|
field_html = []
|
|
173
185
|
hostnames = Config('Hostnames') # Load once outside the loop
|
|
174
186
|
skip_login_db = DataBase("skip_login")
|
|
187
|
+
hostname_issues = get_all_hostname_issues()
|
|
175
188
|
login_required_sites = ['al', 'dd', 'dl', 'nx']
|
|
176
189
|
|
|
177
190
|
for label in shared_state.values["sites"]:
|
|
178
191
|
field_id = label.lower()
|
|
179
|
-
img_html = ''
|
|
180
|
-
try:
|
|
181
|
-
img_data = getattr(images, field_id)
|
|
182
|
-
if img_data:
|
|
183
|
-
img_html = f' <img src="{img_data}" width="16" height="16" style="filter: blur(2px);" alt="{label} icon">'
|
|
184
|
-
except AttributeError:
|
|
185
|
-
pass
|
|
186
192
|
|
|
187
193
|
# Get the current value (if any and non-empty)
|
|
188
194
|
current_value = hostnames.get(field_id)
|
|
189
195
|
if not current_value:
|
|
190
196
|
current_value = '' # Ensure it's empty if None or ""
|
|
191
197
|
|
|
198
|
+
# Determine traffic light status
|
|
199
|
+
is_login_skipped = field_id in login_required_sites and skip_login_db.retrieve(field_id)
|
|
200
|
+
issue = hostname_issues.get(field_id)
|
|
201
|
+
timestamp = ""
|
|
202
|
+
operation = ""
|
|
203
|
+
error_details_for_modal = "" # New variable to hold the full error message for the modal
|
|
204
|
+
|
|
205
|
+
if not current_value:
|
|
206
|
+
status = "unset"
|
|
207
|
+
status_emoji = "⚫️"
|
|
208
|
+
status_title = "Hostname not configured"
|
|
209
|
+
error_details_for_modal = "This hostname is not configured."
|
|
210
|
+
elif is_login_skipped:
|
|
211
|
+
status = "skipped"
|
|
212
|
+
status_emoji = "🟡"
|
|
213
|
+
status_title = "Login was skipped"
|
|
214
|
+
error_details_for_modal = "Login was skipped for this site."
|
|
215
|
+
elif issue:
|
|
216
|
+
status = "error"
|
|
217
|
+
status_emoji = "🔴"
|
|
218
|
+
operation = issue.get("operation", "unknown")
|
|
219
|
+
error_details_for_modal = issue.get("error", "Unknown error") # Get the full error message
|
|
220
|
+
timestamp = issue.get("timestamp", "")
|
|
221
|
+
status_title = f"Error in {operation}"
|
|
222
|
+
else:
|
|
223
|
+
status = "ok"
|
|
224
|
+
status_emoji = "🟢"
|
|
225
|
+
status_title = "Working normally"
|
|
226
|
+
error_details_for_modal = "Configured and working normally."
|
|
227
|
+
|
|
192
228
|
field_html.append(hostname_fields.format(
|
|
193
229
|
id=field_id,
|
|
194
|
-
label=label,
|
|
195
|
-
|
|
196
|
-
|
|
230
|
+
label=_escape_js_for_html_attr(label),
|
|
231
|
+
value=current_value,
|
|
232
|
+
status=status,
|
|
233
|
+
status_emoji=status_emoji,
|
|
234
|
+
status_title=status_title,
|
|
235
|
+
error_details_for_modal=_escape_js_for_html_attr(error_details_for_modal),
|
|
236
|
+
timestamp=timestamp,
|
|
237
|
+
operation=_escape_js_for_html_attr(operation),
|
|
238
|
+
url=_escape_js_for_html_attr(current_value)
|
|
197
239
|
))
|
|
198
240
|
|
|
199
241
|
# Add skip indicator for login-required sites if skip management is enabled
|
|
@@ -254,6 +296,12 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
254
296
|
.import-status.success {{ color: #198754; }}
|
|
255
297
|
.import-status.error {{ color: #dc3545; }}
|
|
256
298
|
.import-status.loading {{ color: var(--secondary, #6c757d); }}
|
|
299
|
+
.status-indicator {{
|
|
300
|
+
transition: transform 0.1s ease;
|
|
301
|
+
}}
|
|
302
|
+
.status-indicator:hover {{
|
|
303
|
+
transform: scale(1.2);
|
|
304
|
+
}}
|
|
257
305
|
.btn-subtle {{
|
|
258
306
|
background: transparent;
|
|
259
307
|
color: var(--fg-color, #212529);
|
|
@@ -274,7 +322,7 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
274
322
|
<div class="url-import-section">
|
|
275
323
|
<h3>📥 Import from URL</h3>
|
|
276
324
|
<div class="url-import-row">
|
|
277
|
-
<input type="
|
|
325
|
+
<input type="url" id="hostnamesUrl" placeholder="https://quasarr-host.name/ini?token=123..." value="{stored_url}" onfocus="onHostnameFieldFocus()">
|
|
278
326
|
<button type="button" class="btn-secondary" id="importBtn" onclick="importHostnames()">Import</button>
|
|
279
327
|
</div>
|
|
280
328
|
<div id="importStatus" class="import-status"></div>
|
|
@@ -313,7 +361,6 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
313
361
|
}}
|
|
314
362
|
|
|
315
363
|
errorDiv.textContent = 'Please fill in at least one hostname!';
|
|
316
|
-
inputs[0].focus();
|
|
317
364
|
return false;
|
|
318
365
|
}}
|
|
319
366
|
|
|
@@ -391,33 +438,38 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
391
438
|
.then(response => response.json())
|
|
392
439
|
.then(data => {{
|
|
393
440
|
if (data.success) {{
|
|
394
|
-
// Remove the skip indicator using the button's parent
|
|
395
441
|
var indicator = btnElement.closest('.skip-indicator');
|
|
396
442
|
if (indicator) indicator.remove();
|
|
397
|
-
|
|
443
|
+
showStatusDetail(shorthand, shorthand.toUpperCase(), 'info', 'Login requirement restored. Restart Quasarr to be prompted for credentials.', '', '', '');
|
|
398
444
|
}} else {{
|
|
399
|
-
|
|
445
|
+
showStatusDetail(shorthand, shorthand.toUpperCase(), 'error', 'Failed to clear skip preference', '', '', '');
|
|
400
446
|
}}
|
|
401
447
|
}})
|
|
402
448
|
.catch(error => {{
|
|
403
|
-
|
|
449
|
+
showStatusDetail(shorthand, shorthand.toUpperCase(), 'error', 'Error: ' + error.message, '', '', '');
|
|
404
450
|
}});
|
|
405
451
|
}}
|
|
406
452
|
|
|
407
453
|
function confirmRestart() {{
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
454
|
+
showModal('Restart Quasarr?', 'Are you sure you want to restart Quasarr now? Any unsaved changes will be lost.',
|
|
455
|
+
`<button class="btn-secondary" onclick="closeModal()">Cancel</button>
|
|
456
|
+
<button class="btn-primary" onclick="performRestart()">Restart</button>`
|
|
457
|
+
);
|
|
458
|
+
}}
|
|
459
|
+
|
|
460
|
+
function performRestart() {{
|
|
461
|
+
closeModal();
|
|
462
|
+
fetch('/api/restart', {{ method: 'POST' }})
|
|
463
|
+
.then(response => response.json())
|
|
464
|
+
.then(data => {{
|
|
465
|
+
if (data.success) {{
|
|
418
466
|
showRestartOverlay();
|
|
419
|
-
}}
|
|
420
|
-
}}
|
|
467
|
+
}}
|
|
468
|
+
}})
|
|
469
|
+
.catch(error => {{
|
|
470
|
+
// Expected - connection will be lost during restart
|
|
471
|
+
showRestartOverlay();
|
|
472
|
+
}});
|
|
421
473
|
}}
|
|
422
474
|
|
|
423
475
|
function showRestartOverlay() {{
|
|
@@ -481,6 +533,69 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
481
533
|
attempt();
|
|
482
534
|
}}
|
|
483
535
|
</script>
|
|
536
|
+
<script>
|
|
537
|
+
function showStatusDetail(id, label, status, error_details, timestamp, operation, url) {{
|
|
538
|
+
var statusTextMap = {{
|
|
539
|
+
ok: 'Operational',
|
|
540
|
+
error: 'Error',
|
|
541
|
+
unset: 'Not configured',
|
|
542
|
+
skipped: 'Login skipped',
|
|
543
|
+
info: 'Information'
|
|
544
|
+
}};
|
|
545
|
+
|
|
546
|
+
var emojiMap = {{
|
|
547
|
+
ok: '🟢',
|
|
548
|
+
error: '🔴',
|
|
549
|
+
unset: '⚫️',
|
|
550
|
+
skipped: '🟡',
|
|
551
|
+
info: 'ℹ️'
|
|
552
|
+
}};
|
|
553
|
+
|
|
554
|
+
var content_html = '';
|
|
555
|
+
if (status === 'error') {{
|
|
556
|
+
content_html += '<p>' + (error_details || 'No details available.') + '</p>';
|
|
557
|
+
}} else {{
|
|
558
|
+
content_html += '<p>' + (error_details || 'No additional details available.') + '</p>';
|
|
559
|
+
}}
|
|
560
|
+
|
|
561
|
+
var timestamp_html = '';
|
|
562
|
+
if (timestamp) {{
|
|
563
|
+
var d = new Date(timestamp);
|
|
564
|
+
var day = ("0" + d.getDate()).slice(-2);
|
|
565
|
+
var month = ("0" + (d.getMonth() + 1)).slice(-2);
|
|
566
|
+
var year = d.getFullYear();
|
|
567
|
+
var hours = ("0" + d.getHours()).slice(-2);
|
|
568
|
+
var minutes = ("0" + d.getMinutes()).slice(-2);
|
|
569
|
+
var seconds = ("0" + d.getSeconds()).slice(-2);
|
|
570
|
+
var formattedTimestamp = day + "." + month + "." + year + " " + hours + ":" + minutes + ":" + seconds;
|
|
571
|
+
|
|
572
|
+
if (operation) {{
|
|
573
|
+
timestamp_html = '<p><small>Occurred in ' + operation + ' at ' + formattedTimestamp + '</small></p>';
|
|
574
|
+
}} else {{
|
|
575
|
+
timestamp_html = '<p><small>Occurred at: ' + formattedTimestamp + '</small></p>';
|
|
576
|
+
}}
|
|
577
|
+
}}
|
|
578
|
+
|
|
579
|
+
var content = content_html + timestamp_html;
|
|
580
|
+
var title = '<span>' + (emojiMap[status] || 'ℹ️') + '</span> ' + label + ' - ' + (statusTextMap[status] || status);
|
|
581
|
+
|
|
582
|
+
var buttons = '';
|
|
583
|
+
if (url) {{
|
|
584
|
+
var href = url;
|
|
585
|
+
if (!href.startsWith('http://') && !href.startsWith('https://')) {{
|
|
586
|
+
href = 'https://' + href;
|
|
587
|
+
}}
|
|
588
|
+
buttons = `
|
|
589
|
+
<button class="btn-primary" style="margin-right: auto;" onclick="window.open('${{href}}', '_blank')">Check ${{id.toUpperCase()}}</button>
|
|
590
|
+
<button class="btn-secondary" onclick="closeModal()">Close</button>
|
|
591
|
+
`;
|
|
592
|
+
}} else {{
|
|
593
|
+
buttons = '<button class="btn-secondary" onclick="closeModal()">Close</button>';
|
|
594
|
+
}}
|
|
595
|
+
|
|
596
|
+
showModal(title, content, buttons);
|
|
597
|
+
}}
|
|
598
|
+
</script>
|
|
484
599
|
"""
|
|
485
600
|
return template.format(
|
|
486
601
|
message=message,
|
|
@@ -719,14 +834,14 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
719
834
|
if (response.ok) {{
|
|
720
835
|
window.location.href = '/skip-success';
|
|
721
836
|
}} else {{
|
|
722
|
-
|
|
837
|
+
showModal('Error', 'Failed to skip login');
|
|
723
838
|
formSubmitted = false;
|
|
724
839
|
if (skipBtn) {{ skipBtn.disabled = false; skipBtn.textContent = 'Skip for now'; }}
|
|
725
840
|
if (submitBtn) {{ submitBtn.disabled = false; }}
|
|
726
841
|
}}
|
|
727
842
|
}})
|
|
728
843
|
.catch(error => {{
|
|
729
|
-
|
|
844
|
+
showModal('Error', 'Error: ' + error.message);
|
|
730
845
|
formSubmitted = false;
|
|
731
846
|
if (skipBtn) {{ skipBtn.disabled = false; skipBtn.textContent = 'Skip for now'; }}
|
|
732
847
|
if (submitBtn) {{ submitBtn.disabled = false; }}
|
|
@@ -870,14 +985,14 @@ def flaresolverr_config(shared_state):
|
|
|
870
985
|
if (response.ok) {{
|
|
871
986
|
window.location.href = '/skip-success';
|
|
872
987
|
}} else {{
|
|
873
|
-
|
|
988
|
+
showModal('Error', 'Failed to skip FlareSolverr setup');
|
|
874
989
|
formSubmitted = false;
|
|
875
990
|
if (skipBtn) {{ skipBtn.disabled = false; skipBtn.textContent = 'Skip for now'; }}
|
|
876
991
|
if (submitBtn) {{ submitBtn.disabled = false; }}
|
|
877
992
|
}}
|
|
878
993
|
}})
|
|
879
994
|
.catch(error => {{
|
|
880
|
-
|
|
995
|
+
showModal('Error', 'Error: ' + error.message);
|
|
881
996
|
formSubmitted = false;
|
|
882
997
|
if (skipBtn) {{ skipBtn.disabled = false; skipBtn.textContent = 'Skip for now'; }}
|
|
883
998
|
if (submitBtn) {{ submitBtn.disabled = false; }}
|
|
@@ -1010,7 +1125,7 @@ def jdownloader_config(shared_state):
|
|
|
1010
1125
|
document.getElementById("verifyButton").style.display = "none";
|
|
1011
1126
|
document.getElementById('deviceForm').style.display = 'block';
|
|
1012
1127
|
} else {
|
|
1013
|
-
|
|
1128
|
+
showModal('Error', 'Fehler! Bitte die Zugangsdaten überprüfen.');
|
|
1014
1129
|
verifyInProgress = false;
|
|
1015
1130
|
if (btn) { btn.disabled = false; btn.textContent = 'Verify Credentials'; }
|
|
1016
1131
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quasarr
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -25,7 +25,7 @@ Dynamic: license-file
|
|
|
25
25
|
Dynamic: requires-dist
|
|
26
26
|
Dynamic: summary
|
|
27
27
|
|
|
28
|
-
#
|
|
28
|
+
#
|
|
29
29
|
|
|
30
30
|
<img src="https://raw.githubusercontent.com/rix1337/Quasarr/main/Quasarr.png" data-canonical-src="https://raw.githubusercontent.com/rix1337/Quasarr/main/Quasarr.png" width="64" height="64" />
|
|
31
31
|
|
|
@@ -125,12 +125,15 @@ Add Quasarr as both a **Newznab Indexer** and **SABnzbd Download Client** using
|
|
|
125
125
|
<details>
|
|
126
126
|
<summary>Restrict results to a specific mirror</summary>
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
1. In the Newznab Settings for Quasarr, enable advanced settings.
|
|
129
|
+
2. Append the desired mirror name to the `API Path` field.
|
|
129
130
|
|
|
130
131
|
```
|
|
131
132
|
/api/dropbox/
|
|
132
133
|
```
|
|
133
134
|
|
|
135
|
+
Using the `URL` field will not work!
|
|
136
|
+
|
|
134
137
|
Only releases with `dropbox` in a link will be returned. If the mirror isn't available, the release will fail.
|
|
135
138
|
|
|
136
139
|
</details>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
quasarr/__init__.py,sha256=cEtxN2AuwKvrxpIvAR7UL997VtYQ4iN3Eo3ZnP-WjZQ,14682
|
|
2
|
+
quasarr/api/__init__.py,sha256=KLnFSe5l3MrVgrbu6-7GlE2PqouVyizqiRZfQkBtge0,19587
|
|
3
|
+
quasarr/api/arr/__init__.py,sha256=eEop8A5t936uT5azn4qz0bq1DMX84_Ja16wyleGFhyM,18495
|
|
4
|
+
quasarr/api/captcha/__init__.py,sha256=Mqg2HhWMaUc07cVaEYHAbf-YvnxkiYVbkWT-g92J-2k,72960
|
|
5
|
+
quasarr/api/config/__init__.py,sha256=kIGCHtKTUovOHe9xMEdz-6_psCmx6aFoyrTP-jJah0s,14187
|
|
6
|
+
quasarr/api/packages/__init__.py,sha256=ox0vzuXByag49RUEwYPWtMacsXl_iksvubHgDmG5RWQ,25192
|
|
7
|
+
quasarr/api/sponsors_helper/__init__.py,sha256=vZIFGkc5HTRozjvi47tqxz6XpwDe8sDXVyeydc9k0Y0,6708
|
|
8
|
+
quasarr/api/statistics/__init__.py,sha256=0Os2rbqQ8ZN3R0XAavGVHlacKsAjp7GYjEIJCwvnsl8,7063
|
|
9
|
+
quasarr/downloads/__init__.py,sha256=ikoHK5C8veDiU4M3eoDaUjFl0pYPSa91_7h65qEFiUM,16435
|
|
10
|
+
quasarr/downloads/linkcrypters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
quasarr/downloads/linkcrypters/al.py,sha256=mfUG5VclC_-FcGoZL9zHYD7dz7X_YpaNmoKkgiyl9-0,8812
|
|
12
|
+
quasarr/downloads/linkcrypters/filecrypt.py,sha256=nUZCTmvKylaNk1KAXcYUV1FgQCVAKNE3roXCNaqHLYA,17057
|
|
13
|
+
quasarr/downloads/linkcrypters/hide.py,sha256=H4hJWhENkszV1u_ULC3aOW2fu9infC-Nv-7wx2DYqrA,6266
|
|
14
|
+
quasarr/downloads/packages/__init__.py,sha256=VUbDnJqhqNDMLwCHYyWQKFKhU32xGMPXdrO0N5fxH8Q,32747
|
|
15
|
+
quasarr/downloads/sources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
quasarr/downloads/sources/al.py,sha256=cV0rOpda4YHmv5X07855wzoaczd3yFGdPyFdm07srFM,27842
|
|
17
|
+
quasarr/downloads/sources/by.py,sha256=ThyEVblYxaxKS_iROpxLhpqp2gOpcHjI1TCKX7CtrNw,4310
|
|
18
|
+
quasarr/downloads/sources/dd.py,sha256=6eOr8FVlbLElBXkoPmKxZ4AQq_Ruq2CimAHHrihHb-A,3372
|
|
19
|
+
quasarr/downloads/sources/dj.py,sha256=wY00hVRNhucZBG1hfExKqayhP1ISD8FFQm7wHYxutOk,404
|
|
20
|
+
quasarr/downloads/sources/dl.py,sha256=zLKVDQS4t_dWbE_I8VlYFOrN28RkBqSHMhJeLcZgqOY,15052
|
|
21
|
+
quasarr/downloads/sources/dt.py,sha256=ycGQtFMOX_8uoeYNjTTCc7FFRCjjwGX6l9FZaCXpfgc,3064
|
|
22
|
+
quasarr/downloads/sources/dw.py,sha256=J0_WhcFJKKNi3cfjwsz7qRM3K8rvNyXC7TjWVRj9Qlc,2743
|
|
23
|
+
quasarr/downloads/sources/he.py,sha256=fmv4f6wrVfJpZtKlQlRq32XCy-I2ROnolBD1CWPyOvY,3986
|
|
24
|
+
quasarr/downloads/sources/mb.py,sha256=ELoHcCmS3iXb0IZXlX444jqIbgrVxQrWB2MwILpjm8c,1826
|
|
25
|
+
quasarr/downloads/sources/nk.py,sha256=XdHzg_u5BfKEU8Mhoim_tiu5oJtECvjNKh2JBal9emA,2052
|
|
26
|
+
quasarr/downloads/sources/nx.py,sha256=IQizLMSa867fZWaOSgVBtGjUMNi06EWIUBMjyAcL6xE,3839
|
|
27
|
+
quasarr/downloads/sources/sf.py,sha256=-IOB3zc94Rd6TzJE59HMmsySNd7feYdqWqNqPhzxjZY,6556
|
|
28
|
+
quasarr/downloads/sources/sj.py,sha256=Bkv0c14AXct50n_viaTNK3bYG-Bpvx8x2D0UN_6gm78,404
|
|
29
|
+
quasarr/downloads/sources/sl.py,sha256=zhU3C172IwyfrVmFSwZ34PywERHJFk0ownT2gbpUAVg,3521
|
|
30
|
+
quasarr/downloads/sources/wd.py,sha256=pZrLnRvVFzsZTWTIcHLCnUlSz5Y6HmEJpqcZjDigx2E,4820
|
|
31
|
+
quasarr/downloads/sources/wx.py,sha256=jk_RSKKPa8iDPXfXKERG_4NwRMkFV6_rJhJ-zZlNQuo,6855
|
|
32
|
+
quasarr/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
quasarr/providers/auth.py,sha256=ELJeKtJL0zy1UuBORzQibN6frAnh3lMv_HiofSW0hXA,10533
|
|
34
|
+
quasarr/providers/cloudflare.py,sha256=oUDR7OQ8E-8vCtagZLnIS2ZZV3ERffhxmW0njKKbtf0,7867
|
|
35
|
+
quasarr/providers/hostname_issues.py,sha256=9PJFIosLB-bMTmgWlR5-sYAmcyps7TDoSYjoL9cw9TE,1460
|
|
36
|
+
quasarr/providers/html_images.py,sha256=rrovPNl-FTTKKA-4HCPEhsYpq5b20VDrsB7t4RrQf3w,15531
|
|
37
|
+
quasarr/providers/html_templates.py,sha256=IGWwt78bP2oJx4VzOP6w9zp7KVXgDY6Qz5ySL9cLGWI,15815
|
|
38
|
+
quasarr/providers/imdb_metadata.py,sha256=10L4kZkt6Fg0HGdNcc6KCtIQHRYEqdarLyaMVN6mT8w,4843
|
|
39
|
+
quasarr/providers/jd_cache.py,sha256=mSvMrs3UwTn3sd9yGSJKGT-qwYeyYKC_l8whpXTVn7s,13530
|
|
40
|
+
quasarr/providers/log.py,sha256=_g5RwtfuksARXnvryhsngzoJyFcNzj6suqd3ndqZM0Y,313
|
|
41
|
+
quasarr/providers/myjd_api.py,sha256=Z3PEiO3c3UfDSr4Up5rgwTAnjloWHb-H1RkJ6BLKZv8,34140
|
|
42
|
+
quasarr/providers/notifications.py,sha256=bohT-6yudmFnmZMc3BwCGX0n1HdzSVgQG_LDZm_38dI,4630
|
|
43
|
+
quasarr/providers/obfuscated.py,sha256=EYm_7SfdJd9ae_m4HZgY9ruDXC5J9hb4KEV_WAnk-ms,2275588
|
|
44
|
+
quasarr/providers/shared_state.py,sha256=5a_ZbGqTvt4-OqBt2a1WtR9I5J_Ky7IlkEY8EGtKVu8,30646
|
|
45
|
+
quasarr/providers/statistics.py,sha256=cEQixYnDMDqtm5wWe40E_2ucyo4mD0n3SrfelhQi1L8,6452
|
|
46
|
+
quasarr/providers/utils.py,sha256=mcUPbcXMsLmrYv0CTZO5a9aOt2-JLyL3SZxu6N8OyjU,12075
|
|
47
|
+
quasarr/providers/version.py,sha256=iakqDG1xdl-OhipfMJ9jdOG8du1BnS6rllqOId1-LAo,4003
|
|
48
|
+
quasarr/providers/web_server.py,sha256=AYd0KRxdDWMBr87BP8wlSMuL4zZo0I_rY-vHBai6Pfg,1688
|
|
49
|
+
quasarr/providers/sessions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
|
+
quasarr/providers/sessions/al.py,sha256=AQ59vVU7uQSuwZLNppNsZAFvpow3zcxQ29dirPbyYc4,13432
|
|
51
|
+
quasarr/providers/sessions/dd.py,sha256=ty9dnDFVJs-tFNcTS5QT9_wP82cKQGnCvb6v5In3Mog,3324
|
|
52
|
+
quasarr/providers/sessions/dl.py,sha256=yTJlD84ItotViA1d-m0RwrbEJlL-VK-0nGw_4kfNLe0,5923
|
|
53
|
+
quasarr/providers/sessions/nx.py,sha256=ZuWuqfb_rPJVom0c1dsXefXPXdzAIYqnQZapOPaUYUI,3421
|
|
54
|
+
quasarr/search/__init__.py,sha256=V59LIiC75mQvasDdTjiWZRbPD1jXO1lhXlKeNVX0iOc,5726
|
|
55
|
+
quasarr/search/sources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
|
+
quasarr/search/sources/al.py,sha256=-2-yRGubyE7bw4-ntGtZ04_LkbnEXhTidfKzKfmeVws,17745
|
|
57
|
+
quasarr/search/sources/by.py,sha256=vNKMEmFpWxpZS9shh5M8gzrOlyyoOc8CveFv6piJ9FM,8344
|
|
58
|
+
quasarr/search/sources/dd.py,sha256=JBQFdCHHv8bOoHAqpzdRSvgVPpRi4udNlFCo9eOoo-M,5593
|
|
59
|
+
quasarr/search/sources/dj.py,sha256=G8O7-Nqx9kuaRtm-kZw1A1Fy0BqorCeNs20qfKF-b_I,7546
|
|
60
|
+
quasarr/search/sources/dl.py,sha256=L4GK58Mp46dAZzmwtMB4ia1w0SSpp3z3eFvrmT-5278,13136
|
|
61
|
+
quasarr/search/sources/dt.py,sha256=hvOqPKQRw5joSaTb9mpdPZXL4xpU167SFmLg8yhsPwM,10227
|
|
62
|
+
quasarr/search/sources/dw.py,sha256=hna1ueKjdi9uqRQJ7UPenT0ym7igQgWGrv_--yGChVs,8215
|
|
63
|
+
quasarr/search/sources/fx.py,sha256=xZUrv7dJSSmeLR2xnRQsRZAk9Q0-fDfQLNjz4wdBTqo,9452
|
|
64
|
+
quasarr/search/sources/he.py,sha256=SoH6X-PsnaOUiQL3yaUbWkI-DDjnyQCMSAwAmv-vpAc,7063
|
|
65
|
+
quasarr/search/sources/mb.py,sha256=Hq1zupo27FzYSQUio03HPG0wP4jYwOXl6cqgdOpjlzQ,8178
|
|
66
|
+
quasarr/search/sources/nk.py,sha256=trb5rTQL_j9br6yBsdSFUp-V4L8_lFYEYpQ4qcB-JlE,6989
|
|
67
|
+
quasarr/search/sources/nx.py,sha256=UXUSYEL4zwYVwCri359I26GYN8CDuCKokpOOR21YEns,7602
|
|
68
|
+
quasarr/search/sources/sf.py,sha256=9k9K8_tYVarpW8n20HA2qAplBL14mIQCsorJO-ZxN6g,15811
|
|
69
|
+
quasarr/search/sources/sj.py,sha256=LW2dVDfZ90mDdrQ6ZYtXb0eOjV3cCh6kEW7lTra1c5M,7608
|
|
70
|
+
quasarr/search/sources/sl.py,sha256=kzojkXw34AFlBfQIOKm8S-iuu9VXLNSfd3VgrkHItbY,10408
|
|
71
|
+
quasarr/search/sources/wd.py,sha256=IvB0Lm8Vb0XI-OwuqCccakhMSQBoTKhEDnvrKDTmP14,7969
|
|
72
|
+
quasarr/search/sources/wx.py,sha256=E7vSLV11540pson1HU-WVb7v7oX67kW8Y3-wiS-mv7w,13844
|
|
73
|
+
quasarr/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
+
quasarr/storage/config.py,sha256=SSTgIce2FVYoVTK_6OCU3msknhxuLA3EC4Kcrrf_dxQ,6378
|
|
75
|
+
quasarr/storage/setup.py,sha256=Cbo0phZbC6JP2wx_qER3vpaLSTDLbKEfdXj6KoAMkWw,47403
|
|
76
|
+
quasarr/storage/sqlite_database.py,sha256=yMqFQfKf0k7YS-6Z3_7pj4z1GwWSXJ8uvF4IydXsuTE,3554
|
|
77
|
+
quasarr-2.2.0.dist-info/licenses/LICENSE,sha256=QQFCAfDgt7lSA8oSWDHIZ9aTjFbZaBJdjnGOHkuhK7k,1060
|
|
78
|
+
quasarr-2.2.0.dist-info/METADATA,sha256=dkJt9lLP1HUd-JSYhswlcYSsHYPz9wlU44gBXFlu7NA,15024
|
|
79
|
+
quasarr-2.2.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
80
|
+
quasarr-2.2.0.dist-info/entry_points.txt,sha256=gXi8mUKsIqKVvn-bOc8E5f04sK_KoMCC-ty6b2Hf-jc,40
|
|
81
|
+
quasarr-2.2.0.dist-info/top_level.txt,sha256=dipJdaRda5ruTZkoGfZU60bY4l9dtPlmOWwxK_oGSF0,8
|
|
82
|
+
quasarr-2.2.0.dist-info/RECORD,,
|