quasarr 2.4.0__tar.gz → 2.4.2__tar.gz
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-2.4.0 → quasarr-2.4.2}/PKG-INFO +1 -1
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/__init__.py +7 -1
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/api/__init__.py +6 -3
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/api/captcha/__init__.py +5 -1
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/api/packages/__init__.py +135 -38
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/api/sponsors_helper/__init__.py +2 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/api/statistics/__init__.py +20 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/packages/__init__.py +7 -2
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/auth.py +1 -1
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/html_images.py +1 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/html_templates.py +1 -1
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/notifications.py +5 -2
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/statistics.py +49 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/version.py +1 -1
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/web_server.py +1 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/storage/setup.py +40 -15
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr.egg-info/PKG-INFO +1 -1
- {quasarr-2.4.0 → quasarr-2.4.2}/LICENSE +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/README.md +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/api/arr/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/api/config/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/linkcrypters/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/linkcrypters/al.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/linkcrypters/filecrypt.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/linkcrypters/hide.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/al.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/by.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/dd.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/dj.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/dl.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/dt.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/dw.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/he.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/mb.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/nk.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/nx.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/sf.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/sj.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/sl.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/wd.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/downloads/sources/wx.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/cloudflare.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/hostname_issues.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/imdb_metadata.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/jd_cache.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/log.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/myjd_api.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/obfuscated.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/sessions/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/sessions/al.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/sessions/dd.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/sessions/dl.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/sessions/nx.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/shared_state.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/providers/utils.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/al.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/by.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/dd.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/dj.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/dl.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/dt.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/dw.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/fx.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/he.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/mb.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/nk.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/nx.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/sf.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/sj.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/sl.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/wd.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/search/sources/wx.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/storage/__init__.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/storage/config.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr/storage/sqlite_database.py +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr.egg-info/SOURCES.txt +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr.egg-info/dependency_links.txt +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr.egg-info/entry_points.txt +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr.egg-info/not-zip-safe +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr.egg-info/requires.txt +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/quasarr.egg-info/top_level.txt +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/setup.cfg +0 -0
- {quasarr-2.4.0 → quasarr-2.4.2}/setup.py +0 -0
|
@@ -12,6 +12,7 @@ import time
|
|
|
12
12
|
|
|
13
13
|
import requests
|
|
14
14
|
|
|
15
|
+
import quasarr.providers.web_server
|
|
15
16
|
from quasarr.api import get_api
|
|
16
17
|
from quasarr.providers import shared_state, version
|
|
17
18
|
from quasarr.providers.log import info, debug
|
|
@@ -157,6 +158,8 @@ def run():
|
|
|
157
158
|
skip_login_db = DataBase("skip_login")
|
|
158
159
|
login_required_sites = ['al', 'dd', 'dl', 'nx']
|
|
159
160
|
|
|
161
|
+
quasarr.providers.web_server.temp_server_success = False
|
|
162
|
+
|
|
160
163
|
for site in login_required_sites:
|
|
161
164
|
hostname = Config('Hostnames').get(site)
|
|
162
165
|
if hostname:
|
|
@@ -164,9 +167,12 @@ def run():
|
|
|
164
167
|
user = site_config.get('user')
|
|
165
168
|
password = site_config.get('password')
|
|
166
169
|
if not user or not password:
|
|
167
|
-
|
|
170
|
+
skip_val = skip_login_db.retrieve(site)
|
|
171
|
+
if skip_val and str(skip_val).lower() == "true":
|
|
168
172
|
info(f'"{site.upper()}" login skipped by user preference')
|
|
169
173
|
else:
|
|
174
|
+
info(f'"{site.upper()}" credentials missing. Launching setup...')
|
|
175
|
+
quasarr.providers.web_server.temp_server_success = False
|
|
170
176
|
hostname_credentials_config(shared_state, site.upper(), hostname)
|
|
171
177
|
|
|
172
178
|
# Check FlareSolverr configuration
|
|
@@ -47,7 +47,7 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
47
47
|
# Get quick status summary
|
|
48
48
|
try:
|
|
49
49
|
device = shared_state.values.get("device")
|
|
50
|
-
jd_connected = device is not None
|
|
50
|
+
jd_connected = device is not None and device is not False
|
|
51
51
|
except:
|
|
52
52
|
jd_connected = False
|
|
53
53
|
|
|
@@ -67,8 +67,10 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
67
67
|
# Skip unset hostnames and skipped logins
|
|
68
68
|
if not current_value:
|
|
69
69
|
continue
|
|
70
|
-
if shorthand in login_required_sites
|
|
71
|
-
|
|
70
|
+
if shorthand in login_required_sites:
|
|
71
|
+
skip_val = skip_login_db.retrieve(shorthand)
|
|
72
|
+
if skip_val and str(skip_val).lower() == "true":
|
|
73
|
+
continue
|
|
72
74
|
|
|
73
75
|
# This hostname counts toward total
|
|
74
76
|
total_count += 1
|
|
@@ -474,6 +476,7 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
474
476
|
|
|
475
477
|
@app.get('/regenerate-api-key')
|
|
476
478
|
def regenerate_api_key():
|
|
479
|
+
shared_state.generate_api_key()
|
|
477
480
|
return render_success(f'API key replaced!', 5)
|
|
478
481
|
|
|
479
482
|
Server(app, listen='0.0.0.0', port=shared_state.values["port"]).serve_forever()
|
|
@@ -48,7 +48,11 @@ def setup_captcha_routes(app):
|
|
|
48
48
|
device = None
|
|
49
49
|
if not device:
|
|
50
50
|
return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
51
|
-
<
|
|
51
|
+
<div class="status-bar">
|
|
52
|
+
<span class="status-pill error">
|
|
53
|
+
❌ JDownloader disconnected
|
|
54
|
+
</span>
|
|
55
|
+
</div>
|
|
52
56
|
<p>
|
|
53
57
|
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
54
58
|
</p>''')
|
|
@@ -41,24 +41,30 @@ def _render_queue_item(item):
|
|
|
41
41
|
cat = item.get('cat', 'not_quasarr')
|
|
42
42
|
is_archive = item.get('is_archive', False)
|
|
43
43
|
nzo_id = item.get('nzo_id', '')
|
|
44
|
+
storage = item.get('storage', '')
|
|
44
45
|
|
|
45
46
|
is_captcha = '[CAPTCHA' in filename
|
|
47
|
+
status_text = 'Downloading'
|
|
46
48
|
if is_captcha:
|
|
47
49
|
status_emoji = '🔒'
|
|
50
|
+
status_text = 'Waiting for CAPTCHA Solution!'
|
|
48
51
|
elif '[Extracting]' in filename:
|
|
49
52
|
status_emoji = '📦'
|
|
53
|
+
status_text = 'Extracting'
|
|
50
54
|
elif '[Paused]' in filename:
|
|
51
55
|
status_emoji = '⏸️'
|
|
56
|
+
status_text = 'Paused'
|
|
52
57
|
elif '[Linkgrabber]' in filename:
|
|
53
58
|
status_emoji = '🔗'
|
|
59
|
+
status_text = 'Linkgrabber'
|
|
54
60
|
else:
|
|
55
|
-
status_emoji = '
|
|
61
|
+
status_emoji = '▶️'
|
|
56
62
|
|
|
57
63
|
display_name = filename
|
|
58
64
|
for prefix in ['[Downloading] ', '[Extracting] ', '[Paused] ', '[Linkgrabber] ', '[CAPTCHA not solved!] ']:
|
|
59
65
|
display_name = display_name.replace(prefix, '')
|
|
60
66
|
|
|
61
|
-
archive_badge = '
|
|
67
|
+
archive_badge = '📦' if is_archive else ''
|
|
62
68
|
cat_emoji = _get_category_emoji(cat)
|
|
63
69
|
size_str = _format_size(bytes_val=bytes_val) if bytes_val else _format_size(mb=mb)
|
|
64
70
|
|
|
@@ -68,23 +74,38 @@ def _render_queue_item(item):
|
|
|
68
74
|
else:
|
|
69
75
|
progress_html = f'<div class="progress-track"><div class="progress-fill" style="width: {percentage}%"></div></div>'
|
|
70
76
|
|
|
71
|
-
#
|
|
77
|
+
# Interactive info
|
|
78
|
+
info_onclick = f"showPackageDetails('{nzo_id}', '{_escape_js(display_name)}', '{cat}', '{'Yes' if is_archive else 'No'}', '', '{timeleft}', '{size_str}', '{percentage}', '{status_text}', '{_escape_js(storage)}', {str(is_captcha).lower()})"
|
|
79
|
+
info_btn = f'<button class="btn-small info" onclick="{info_onclick}">ℹ️</button>'
|
|
80
|
+
|
|
81
|
+
# Action buttons - Info left, CAPTCHA/Delete right
|
|
72
82
|
if is_captcha and nzo_id:
|
|
73
83
|
actions = f'''
|
|
74
84
|
<div class="package-actions">
|
|
75
|
-
|
|
85
|
+
{info_btn}
|
|
86
|
+
<button class="btn-small primary-thin" onclick="location.href='/captcha?package_id={nzo_id}'">🔓 Solve CAPTCHA</button>
|
|
76
87
|
<span class="spacer"></span>
|
|
77
88
|
<button class="btn-small danger" onclick="confirmDelete('{nzo_id}', '{_escape_js(display_name)}')">🗑️</button>
|
|
78
89
|
</div>
|
|
79
90
|
'''
|
|
80
91
|
elif nzo_id:
|
|
81
92
|
actions = f'''
|
|
82
|
-
<div class="package-actions
|
|
93
|
+
<div class="package-actions">
|
|
94
|
+
{info_btn}
|
|
95
|
+
<span class="spacer"></span>
|
|
83
96
|
<button class="btn-small danger" onclick="confirmDelete('{nzo_id}', '{_escape_js(display_name)}')">🗑️</button>
|
|
84
97
|
</div>
|
|
85
98
|
'''
|
|
86
99
|
else:
|
|
87
|
-
actions = ''
|
|
100
|
+
actions = f'''
|
|
101
|
+
<div class="package-actions">
|
|
102
|
+
{info_btn}
|
|
103
|
+
<span class="spacer"></span>
|
|
104
|
+
</div>
|
|
105
|
+
'''
|
|
106
|
+
|
|
107
|
+
cat_html = f'<span title="Category: {cat}">{cat_emoji}</span>'
|
|
108
|
+
archive_html = f'<span title="Archive: {is_archive}">{archive_badge}</span>' if is_archive else ''
|
|
88
109
|
|
|
89
110
|
return f'''
|
|
90
111
|
<div class="package-card">
|
|
@@ -99,8 +120,8 @@ def _render_queue_item(item):
|
|
|
99
120
|
<div class="package-details">
|
|
100
121
|
<span>⏱️ {timeleft}</span>
|
|
101
122
|
<span>💾 {size_str}</span>
|
|
102
|
-
|
|
103
|
-
{
|
|
123
|
+
{cat_html}
|
|
124
|
+
{archive_html}
|
|
104
125
|
</div>
|
|
105
126
|
{actions}
|
|
106
127
|
</div>
|
|
@@ -116,6 +137,7 @@ def _render_history_item(item):
|
|
|
116
137
|
extraction_status = item.get('extraction_status', '')
|
|
117
138
|
fail_message = item.get('fail_message', '')
|
|
118
139
|
nzo_id = item.get('nzo_id', '')
|
|
140
|
+
storage = item.get('storage', '')
|
|
119
141
|
|
|
120
142
|
is_error = status.lower() in ['failed', 'error'] or fail_message
|
|
121
143
|
card_class = 'package-card error' if is_error else 'package-card'
|
|
@@ -123,27 +145,41 @@ def _render_history_item(item):
|
|
|
123
145
|
cat_emoji = _get_category_emoji(category)
|
|
124
146
|
size_str = _format_size(bytes_val=bytes_val)
|
|
125
147
|
|
|
126
|
-
|
|
148
|
+
archive_emoji = ''
|
|
127
149
|
if is_archive:
|
|
128
150
|
if extraction_status == 'SUCCESSFUL':
|
|
129
|
-
|
|
151
|
+
archive_emoji = '✅'
|
|
130
152
|
elif extraction_status == 'RUNNING':
|
|
131
|
-
|
|
153
|
+
archive_emoji = '⏳'
|
|
132
154
|
else:
|
|
133
|
-
|
|
155
|
+
archive_emoji = '📦'
|
|
134
156
|
|
|
135
157
|
status_emoji = '❌' if is_error else '✅'
|
|
136
158
|
error_html = f'<div class="package-error">⚠️ {fail_message}</div>' if fail_message else ''
|
|
137
159
|
|
|
160
|
+
# Interactive info
|
|
161
|
+
info_onclick = f"showPackageDetails('{nzo_id}', '{_escape_js(name)}', '{category}', '{'Yes' if is_archive else 'No'}', '{extraction_status}', '', '{size_str}', '', '{status}', '{_escape_js(storage)}', false)"
|
|
162
|
+
info_btn = f'<button class="btn-small info" onclick="{info_onclick}">ℹ️</button>'
|
|
163
|
+
|
|
138
164
|
# Delete button for history items
|
|
139
165
|
if nzo_id:
|
|
140
166
|
actions = f'''
|
|
141
|
-
<div class="package-actions
|
|
167
|
+
<div class="package-actions">
|
|
168
|
+
{info_btn}
|
|
169
|
+
<span class="spacer"></span>
|
|
142
170
|
<button class="btn-small danger" onclick="confirmDelete('{nzo_id}', '{_escape_js(name)}')">🗑️</button>
|
|
143
171
|
</div>
|
|
144
172
|
'''
|
|
145
173
|
else:
|
|
146
|
-
actions = ''
|
|
174
|
+
actions = f'''
|
|
175
|
+
<div class="package-actions">
|
|
176
|
+
{info_btn}
|
|
177
|
+
<span class="spacer"></span>
|
|
178
|
+
</div>
|
|
179
|
+
'''
|
|
180
|
+
|
|
181
|
+
cat_html = f'<span title="Category: {category}">{cat_emoji}</span>'
|
|
182
|
+
archive_html = f'<span title="Archive Status: {extraction_status}">{archive_emoji}</span>' if is_archive else ''
|
|
147
183
|
|
|
148
184
|
return f'''
|
|
149
185
|
<div class="{card_class}">
|
|
@@ -153,8 +189,8 @@ def _render_history_item(item):
|
|
|
153
189
|
</div>
|
|
154
190
|
<div class="package-details">
|
|
155
191
|
<span>💾 {size_str}</span>
|
|
156
|
-
|
|
157
|
-
{
|
|
192
|
+
{cat_html}
|
|
193
|
+
{archive_html}
|
|
158
194
|
</div>
|
|
159
195
|
{error_html}
|
|
160
196
|
{actions}
|
|
@@ -261,7 +297,13 @@ def setup_packages_routes(app):
|
|
|
261
297
|
device = None
|
|
262
298
|
|
|
263
299
|
if not device:
|
|
264
|
-
return '
|
|
300
|
+
return '''
|
|
301
|
+
<div class="status-bar">
|
|
302
|
+
<span class="status-pill error">
|
|
303
|
+
❌ JDownloader disconnected
|
|
304
|
+
</span>
|
|
305
|
+
</div>
|
|
306
|
+
'''
|
|
265
307
|
|
|
266
308
|
return _render_packages_content()
|
|
267
309
|
|
|
@@ -278,7 +320,11 @@ def setup_packages_routes(app):
|
|
|
278
320
|
back_btn = render_button("Back", "secondary", {"onclick": "location.href='/'"})
|
|
279
321
|
return render_centered_html(f'''
|
|
280
322
|
<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
|
|
281
|
-
<
|
|
323
|
+
<div class="status-bar">
|
|
324
|
+
<span class="status-pill error">
|
|
325
|
+
❌ JDownloader disconnected
|
|
326
|
+
</span>
|
|
327
|
+
</div>
|
|
282
328
|
<p>{back_btn}</p>
|
|
283
329
|
''')
|
|
284
330
|
|
|
@@ -328,12 +374,6 @@ def setup_packages_routes(app):
|
|
|
328
374
|
.package-header {{ display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px; }}
|
|
329
375
|
.status-emoji {{ font-size: 1.2em; flex-shrink: 0; }}
|
|
330
376
|
.package-name {{ flex: 1; font-weight: 500; word-break: break-word; line-height: 1.3; }}
|
|
331
|
-
|
|
332
|
-
.badge {{ font-size: 0.75em; padding: 2px 6px; border-radius: 4px; white-space: nowrap; flex-shrink: 0; }}
|
|
333
|
-
.badge.archive {{ background: var(--badge-archive-bg, #e3f2fd); color: var(--badge-archive-color, #1565c0); }}
|
|
334
|
-
.badge.extracted {{ background: var(--badge-success-bg, #e8f5e9); color: var(--badge-success-color, #2e7d32); }}
|
|
335
|
-
.badge.pending {{ background: var(--badge-warning-bg, #fff3e0); color: var(--badge-warning-color, #e65100); }}
|
|
336
|
-
|
|
337
377
|
.package-progress {{ display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }}
|
|
338
378
|
.progress-track {{ flex: 1; height: 8px; background: var(--progress-track, #e0e0e0); border-radius: 4px; overflow: hidden; }}
|
|
339
379
|
.progress-fill {{ height: 100%; background: var(--progress-fill, #4caf50); border-radius: 4px; min-width: 4px; }}
|
|
@@ -346,11 +386,15 @@ def setup_packages_routes(app):
|
|
|
346
386
|
.package-actions {{ margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-color, #eee); display: flex; gap: 8px; align-items: center; }}
|
|
347
387
|
.package-actions .spacer {{ flex: 1; }}
|
|
348
388
|
.package-actions.right-only {{ justify-content: flex-end; }}
|
|
349
|
-
.btn-small {{ padding: 5px 12px; font-size: 0.8em; border-radius: 4px; cursor: pointer; transition: all 0.2s; }}
|
|
389
|
+
.btn-small {{ line-height:1; padding: 5px 12px; font-size: 0.8em; border-radius: 4px; cursor: pointer; transition: all 0.2s; }}
|
|
350
390
|
.btn-small.primary {{ background: var(--btn-primary-bg, #007bff); color: white; border: none; }}
|
|
351
391
|
.btn-small.primary:hover {{ background: var(--btn-primary-hover, #0056b3); }}
|
|
352
392
|
.btn-small.danger {{ background: transparent; color: var(--btn-danger-text, #dc3545); border: 1px solid var(--btn-danger-border, #dc3545); }}
|
|
353
393
|
.btn-small.danger:hover {{ background: var(--btn-danger-hover-bg, #dc3545); color: white; }}
|
|
394
|
+
.btn-small.info {{ background: transparent; color: var(--btn-info-bg, #17a2b8); border: 1px solid var(--btn-info-bg, #17a2b8); }}
|
|
395
|
+
.btn-small.info:hover {{ background: var(--btn-info-bg, #17a2b8); color: white; }}
|
|
396
|
+
.btn-small.primary-thin {{ background: transparent; color: var(--btn-primary-bg, #007bff); border: 1px solid var(--btn-primary-bg, #007bff); }}
|
|
397
|
+
.btn-small.primary-thin:hover {{ background: var(--btn-primary-bg, #007bff); color: white; }}
|
|
354
398
|
|
|
355
399
|
.empty-message {{ color: var(--text-muted, #888); font-style: italic; text-align: center; padding: 20px; }}
|
|
356
400
|
|
|
@@ -397,8 +441,8 @@ def setup_packages_routes(app):
|
|
|
397
441
|
border: 1px solid var(--error-border, #f1aeb5);
|
|
398
442
|
}}
|
|
399
443
|
|
|
400
|
-
.btn-danger {{ background: var(--btn-danger-bg, #dc3545);
|
|
401
|
-
.btn-danger:hover {{
|
|
444
|
+
.btn-danger {{ background: transparent; color: var(--btn-danger-bg, #dc3545); border: 1px solid var(--btn-danger-bg, #dc3545); padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 500; }}
|
|
445
|
+
.btn-danger:hover {{ background: var(--btn-danger-bg, #dc3545); color: white; }}
|
|
402
446
|
|
|
403
447
|
/* Dark mode */
|
|
404
448
|
@media (prefers-color-scheme: dark) {{
|
|
@@ -407,13 +451,11 @@ def setup_packages_routes(app):
|
|
|
407
451
|
--border-color: #4a5568; --text-muted: #a0aec0;
|
|
408
452
|
--progress-track: #4a5568; --progress-fill: #68d391;
|
|
409
453
|
--error-border: #fc8181; --error-bg: #3d2d2d; --error-msg-bg: #3d2d2d; --error-msg-color: #fc8181;
|
|
410
|
-
--badge-archive-bg: #1a365d; --badge-archive-color: #63b3ed;
|
|
411
|
-
--badge-success-bg: #1c4532; --badge-success-color: #68d391;
|
|
412
|
-
--badge-warning-bg: #3d2d1a; --badge-warning-color: #f6ad55;
|
|
413
454
|
--link-color: #63b3ed; --modal-bg: #2d3748; --code-bg: #1a202c;
|
|
414
455
|
--btn-primary-bg: #3182ce; --btn-primary-hover: #2c5282;
|
|
415
456
|
--btn-danger-text: #fc8181; --btn-danger-border: #fc8181; --btn-danger-hover-bg: #e53e3e;
|
|
416
457
|
--success-bg: #1c4532; --success-color: #68d391; --success-border: #276749;
|
|
458
|
+
--btn-info-bg: #38b2ac; --btn-info-hover: #319795;
|
|
417
459
|
}}
|
|
418
460
|
}}
|
|
419
461
|
</style>
|
|
@@ -422,10 +464,14 @@ def setup_packages_routes(app):
|
|
|
422
464
|
// Background refresh - fetches content via AJAX, waits 5s between refresh cycles
|
|
423
465
|
let refreshPaused = false;
|
|
424
466
|
let slowConnection = false;
|
|
467
|
+
let refreshTimer = null;
|
|
468
|
+
let isFetching = false;
|
|
425
469
|
|
|
426
470
|
async function refreshContent() {{
|
|
427
471
|
if (refreshPaused) return;
|
|
472
|
+
if (isFetching) return;
|
|
428
473
|
|
|
474
|
+
isFetching = true;
|
|
429
475
|
const startTime = Date.now();
|
|
430
476
|
const warningEl = document.getElementById('slow-warning');
|
|
431
477
|
|
|
@@ -465,8 +511,15 @@ def setup_packages_routes(app):
|
|
|
465
511
|
}}
|
|
466
512
|
}} catch (e) {{
|
|
467
513
|
clearTimeout(slowTimer);
|
|
514
|
+
}} finally {{
|
|
515
|
+
isFetching = false;
|
|
516
|
+
}}
|
|
517
|
+
|
|
518
|
+
// Only schedule next refresh if not paused
|
|
519
|
+
if (!refreshPaused) {{
|
|
520
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
521
|
+
refreshTimer = setTimeout(refreshContent, 5000);
|
|
468
522
|
}}
|
|
469
|
-
setTimeout(refreshContent, 5000);
|
|
470
523
|
}}
|
|
471
524
|
|
|
472
525
|
function restoreCollapseState() {{
|
|
@@ -520,6 +573,10 @@ def setup_packages_routes(app):
|
|
|
520
573
|
// Delete modal
|
|
521
574
|
let deletePackageId = null;
|
|
522
575
|
function confirmDelete(packageId, packageName) {{
|
|
576
|
+
// Stop any pending refresh
|
|
577
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
578
|
+
refreshPaused = true;
|
|
579
|
+
|
|
523
580
|
deletePackageId = packageId;
|
|
524
581
|
|
|
525
582
|
const content = `
|
|
@@ -530,12 +587,11 @@ def setup_packages_routes(app):
|
|
|
530
587
|
`;
|
|
531
588
|
|
|
532
589
|
const buttons = `
|
|
533
|
-
<button class="btn-secondary" onclick="closeModal()">
|
|
590
|
+
<button class="btn-secondary" onclick="closeModal()">Back</button>
|
|
534
591
|
<button class="btn-danger" onclick="performDelete()">🗑️ Delete Package & Files</button>
|
|
535
592
|
`;
|
|
536
593
|
|
|
537
594
|
showModal('🗑️ Delete Package?', content, buttons);
|
|
538
|
-
refreshPaused = true;
|
|
539
595
|
}}
|
|
540
596
|
|
|
541
597
|
function performDelete() {{
|
|
@@ -544,12 +600,53 @@ def setup_packages_routes(app):
|
|
|
544
600
|
}}
|
|
545
601
|
}}
|
|
546
602
|
|
|
603
|
+
// Show package details modal
|
|
604
|
+
function showPackageDetails(id, name, category, isArchive, extractionStatus, eta, size, percentage, status, storage, isCaptcha) {{
|
|
605
|
+
// Stop any pending refresh
|
|
606
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
607
|
+
refreshPaused = true;
|
|
608
|
+
|
|
609
|
+
let captchaBtn = '';
|
|
610
|
+
if (isCaptcha) {{
|
|
611
|
+
captchaBtn = `<button class="btn-small primary-thin" onclick="location.href='/captcha?package_id=${{id}}'">🔓 Solve CAPTCHA</button>`;
|
|
612
|
+
}}
|
|
613
|
+
|
|
614
|
+
const content = `
|
|
615
|
+
<div style="text-align: left; padding: 10px;">
|
|
616
|
+
<p style="margin-bottom: 8px;"><strong>Name:</strong></p><p style="font-family: monospace; text-align: center; background: var(--code-bg, #eee); padding: 4px; border-radius: 4px; word-break: break-word;">${{name}}</p>
|
|
617
|
+
${{storage ? `<p style="margin-bottom: 4px;"><strong>Storage:</strong></p><p style="margin-bottom: 8px; font-family: monospace; text-align: center; background: var(--code-bg, #eee); padding: 4px; border-radius: 4px; word-break: break-all;">${{storage}}</p>` : ''}}
|
|
618
|
+
<p style="margin-bottom: 4px;"><strong>ID:</strong></p><p style="margin-bottom: 8px; font-family: monospace; text-align: center; background: var(--code-bg, #eee); padding: 4px; border-radius: 4px;">${{id}}</p>
|
|
619
|
+
<p style="margin-bottom: 8px;"><strong>Status:</strong> ${{status}}</p>
|
|
620
|
+
${{percentage ? `<p style="margin-bottom: 8px;"><strong>Percentage:</strong> ${{percentage}}%</p>` : ''}}
|
|
621
|
+
${{size ? `<p style="margin-bottom: 8px;"><strong>Size:</strong> ${{size}}</p>` : ''}}
|
|
622
|
+
${{eta ? `<p style="margin-bottom: 8px;"><strong>ETA:</strong> ${{eta}}</p>` : ''}}
|
|
623
|
+
<p style="margin-bottom: 8px;"><strong>Category:</strong> ${{category}}</p>
|
|
624
|
+
<p style="margin-bottom: 8px;"><strong>Archive:</strong> ${{isArchive}}</p>
|
|
625
|
+
${{extractionStatus ? `<p style="margin-bottom: 8px;"><strong>Extraction Status:</strong> ${{extractionStatus}}</p>` : ''}}
|
|
626
|
+
</div>
|
|
627
|
+
`;
|
|
628
|
+
|
|
629
|
+
const buttons = `
|
|
630
|
+
<button class="btn-secondary" onclick="closeModal()">Back</button>
|
|
631
|
+
${{captchaBtn}}
|
|
632
|
+
`;
|
|
633
|
+
|
|
634
|
+
showModal('ℹ️ Package Details', content, buttons);
|
|
635
|
+
}}
|
|
636
|
+
|
|
547
637
|
// Hook into modal closing to resume refresh
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
638
|
+
document.addEventListener('DOMContentLoaded', function() {{
|
|
639
|
+
const baseCloseModal = window.closeModal;
|
|
640
|
+
window.closeModal = function() {{
|
|
641
|
+
if (baseCloseModal) baseCloseModal();
|
|
642
|
+
|
|
643
|
+
// Clear any existing timer to prevent duplicates
|
|
644
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
645
|
+
|
|
646
|
+
refreshPaused = false;
|
|
647
|
+
refreshContent();
|
|
648
|
+
}};
|
|
649
|
+
}});
|
|
553
650
|
</script>
|
|
554
651
|
'''
|
|
555
652
|
|
|
@@ -121,6 +121,8 @@ def setup_sponsors_helper_routes(app):
|
|
|
121
121
|
|
|
122
122
|
StatsHelper(shared_state).increment_captcha_decryptions_automatic()
|
|
123
123
|
|
|
124
|
+
send_discord_message(shared_state, title=title, case="disabled")
|
|
125
|
+
|
|
124
126
|
return f"Package {title} disabled"
|
|
125
127
|
|
|
126
128
|
except Exception as e:
|
|
@@ -72,6 +72,26 @@ def setup_statistics(app, shared_state):
|
|
|
72
72
|
<div class="stat-value">{stats['failed_decryptions_manual']:,}</div>
|
|
73
73
|
</div>
|
|
74
74
|
</div>
|
|
75
|
+
|
|
76
|
+
<h3>🎬 IMDb Cache</h3>
|
|
77
|
+
<div class="stats-grid compact">
|
|
78
|
+
<div class="stat-card">
|
|
79
|
+
<h3>💾 Total Cached IDs</h3>
|
|
80
|
+
<div class="stat-value">{stats['imdb_total_cached']:,}</div>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="stat-card">
|
|
83
|
+
<h3>🏷️ With Title</h3>
|
|
84
|
+
<div class="stat-value">{stats['imdb_with_title']:,}</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="stat-card">
|
|
87
|
+
<h3>🖼️ With Poster</h3>
|
|
88
|
+
<div class="stat-value">{stats['imdb_with_poster']:,}</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="stat-card">
|
|
91
|
+
<h3>🌍 With Localized Title</h3>
|
|
92
|
+
<div class="stat-value">{stats['imdb_with_localized']:,}</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
75
95
|
</div>
|
|
76
96
|
|
|
77
97
|
<p>
|
|
@@ -463,10 +463,12 @@ def get_packages(shared_state, _cache=None):
|
|
|
463
463
|
|
|
464
464
|
if package["location"] == "queue":
|
|
465
465
|
time_left = "23:59:59"
|
|
466
|
+
storage = ""
|
|
466
467
|
|
|
467
468
|
if package["type"] == "linkgrabber":
|
|
468
469
|
details = package["details"]
|
|
469
470
|
name = f"[Linkgrabber] {details.get('name', 'unknown')}"
|
|
471
|
+
storage = details.get("saveTo", "")
|
|
470
472
|
try:
|
|
471
473
|
bytes_total = int(details.get("bytesTotal", 0))
|
|
472
474
|
mb = mb_left = bytes_total / (1024 * 1024)
|
|
@@ -481,6 +483,7 @@ def get_packages(shared_state, _cache=None):
|
|
|
481
483
|
elif package["type"] == "downloader":
|
|
482
484
|
details = package["details"]
|
|
483
485
|
status = "Downloading"
|
|
486
|
+
storage = details.get("saveTo", "")
|
|
484
487
|
pkg_eta = details.get("eta")
|
|
485
488
|
bytes_total = int(details.get("bytesTotal", 0))
|
|
486
489
|
bytes_loaded = int(details.get("bytesLoaded", 0))
|
|
@@ -536,7 +539,8 @@ def get_packages(shared_state, _cache=None):
|
|
|
536
539
|
"timeleft": time_left,
|
|
537
540
|
"type": package_type,
|
|
538
541
|
"uuid": package_uuid,
|
|
539
|
-
"is_archive": package.get("is_archive", False)
|
|
542
|
+
"is_archive": package.get("is_archive", False),
|
|
543
|
+
"storage": storage
|
|
540
544
|
})
|
|
541
545
|
queue_index += 1
|
|
542
546
|
else:
|
|
@@ -577,7 +581,8 @@ def get_packages(shared_state, _cache=None):
|
|
|
577
581
|
"type": "downloader",
|
|
578
582
|
"uuid": package.get("uuid"),
|
|
579
583
|
"is_archive": package.get("is_archive", False),
|
|
580
|
-
"extraction_ok": package.get("extraction_ok", False)
|
|
584
|
+
"extraction_ok": package.get("extraction_ok", False),
|
|
585
|
+
"extraction_status": "SUCCESSFUL" if package.get("extraction_ok", False) else "RUNNING" if package.get("is_archive", False) else ""
|
|
581
586
|
})
|
|
582
587
|
history_index += 1
|
|
583
588
|
else:
|
|
@@ -148,7 +148,7 @@ def _render_login_page(error=None):
|
|
|
148
148
|
<meta charset="utf-8">
|
|
149
149
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
150
150
|
<title>Quasarr - Login</title>
|
|
151
|
-
<link rel="icon" href="{images.
|
|
151
|
+
<link rel="icon" href="{images.favicon}" type="image/png">
|
|
152
152
|
<style>
|
|
153
153
|
:root {{
|
|
154
154
|
--bg-color: #ffffff;
|
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
# Quasarr
|
|
3
3
|
# Project by https://github.com/rix1337
|
|
4
4
|
|
|
5
|
+
favicon = 'data:image/png;base64,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'
|
|
5
6
|
logo = 'data:image/webp;base64,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'
|
|
@@ -12,7 +12,7 @@ def render_centered_html(inner_content, footer_content=""):
|
|
|
12
12
|
<meta charset="utf-8">
|
|
13
13
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
14
14
|
<title>Quasarr</title>
|
|
15
|
-
<link rel="icon" href="''' + images.
|
|
15
|
+
<link rel="icon" href="''' + images.favicon + '''" type="image/png">
|
|
16
16
|
<style>
|
|
17
17
|
/* Theme variables */
|
|
18
18
|
:root {
|
|
@@ -53,7 +53,10 @@ def send_discord_message(shared_state, title, case, imdb_id=None, details=None,
|
|
|
53
53
|
description = 'CAPTCHA solved by SponsorsHelper!'
|
|
54
54
|
fields = None
|
|
55
55
|
elif case == "failed":
|
|
56
|
-
description = 'SponsorsHelper failed to solve the CAPTCHA! Package marked
|
|
56
|
+
description = 'SponsorsHelper failed to solve the CAPTCHA! Package marked as failed for deletion.'
|
|
57
|
+
fields = None
|
|
58
|
+
elif case == "disabled":
|
|
59
|
+
description = 'SponsorsHelper failed to solve the CAPTCHA! Please solve it manually to proceed.'
|
|
57
60
|
fields = None
|
|
58
61
|
elif case == "captcha":
|
|
59
62
|
description = 'Download will proceed, once the CAPTCHA has been solved.'
|
|
@@ -112,7 +115,7 @@ def send_discord_message(shared_state, title, case, imdb_id=None, details=None,
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
# Apply silent mode: suppress notifications for all cases except 'deleted'
|
|
115
|
-
if silent and case not in ["failed", "quasarr_update"]:
|
|
118
|
+
if silent and case not in ["failed", "quasarr_update", "disabled"]:
|
|
116
119
|
data['flags'] = SUPPRESS_NOTIFICATIONS
|
|
117
120
|
|
|
118
121
|
response = requests.post(shared_state.values["discord"], data=json.dumps(data),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Project by https://github.com/rix1337
|
|
4
4
|
|
|
5
5
|
from typing import Dict, Any
|
|
6
|
+
from json import loads
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class StatsHelper:
|
|
@@ -101,6 +102,51 @@ class StatsHelper:
|
|
|
101
102
|
"""Increment failed manual decryptions counter"""
|
|
102
103
|
self._increment_stat("failed_decryptions_manual", 1)
|
|
103
104
|
|
|
105
|
+
def get_imdb_cache_stats(self) -> Dict[str, int]:
|
|
106
|
+
"""
|
|
107
|
+
Get statistics about the IMDb metadata cache.
|
|
108
|
+
Returns counts of cached items with various attributes.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
db = self.shared_state.values["database"]("imdb_metadata")
|
|
112
|
+
all_entries = db.retrieve_all_titles()
|
|
113
|
+
|
|
114
|
+
total_cached = 0
|
|
115
|
+
with_title = 0
|
|
116
|
+
with_poster = 0
|
|
117
|
+
with_localized = 0
|
|
118
|
+
|
|
119
|
+
for _, data_str in all_entries:
|
|
120
|
+
try:
|
|
121
|
+
data = loads(data_str)
|
|
122
|
+
total_cached += 1
|
|
123
|
+
|
|
124
|
+
if data.get("title"):
|
|
125
|
+
with_title += 1
|
|
126
|
+
|
|
127
|
+
if data.get("poster_link"):
|
|
128
|
+
with_poster += 1
|
|
129
|
+
|
|
130
|
+
if data.get("localized") and isinstance(data["localized"], dict) and len(data["localized"]) > 0:
|
|
131
|
+
with_localized += 1
|
|
132
|
+
|
|
133
|
+
except (ValueError, TypeError):
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
"imdb_total_cached": total_cached,
|
|
138
|
+
"imdb_with_title": with_title,
|
|
139
|
+
"imdb_with_poster": with_poster,
|
|
140
|
+
"imdb_with_localized": with_localized
|
|
141
|
+
}
|
|
142
|
+
except Exception:
|
|
143
|
+
return {
|
|
144
|
+
"imdb_total_cached": 0,
|
|
145
|
+
"imdb_with_title": 0,
|
|
146
|
+
"imdb_with_poster": 0,
|
|
147
|
+
"imdb_with_localized": 0
|
|
148
|
+
}
|
|
149
|
+
|
|
104
150
|
def get_stats(self) -> Dict[str, Any]:
|
|
105
151
|
"""Get all current statistics"""
|
|
106
152
|
stats = {
|
|
@@ -151,4 +197,7 @@ class StatsHelper:
|
|
|
151
197
|
)
|
|
152
198
|
})
|
|
153
199
|
|
|
200
|
+
# Add IMDb cache stats
|
|
201
|
+
stats.update(self.get_imdb_cache_stats())
|
|
202
|
+
|
|
154
203
|
return stats
|
|
@@ -154,6 +154,7 @@ def path_config(shared_state):
|
|
|
154
154
|
|
|
155
155
|
info(f'Starting web server for config at: "{shared_state.values['internal_address']}".')
|
|
156
156
|
info("Please set desired config path there!")
|
|
157
|
+
quasarr.providers.web_server.temp_server_success = False
|
|
157
158
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
158
159
|
|
|
159
160
|
|
|
@@ -161,7 +162,8 @@ def _escape_js_for_html_attr(s):
|
|
|
161
162
|
"""Escape a string for use inside a JS string literal within an HTML attribute."""
|
|
162
163
|
if s is None:
|
|
163
164
|
return ""
|
|
164
|
-
return str(s).replace("\\", "\\\\").replace("'", "\\'").replace('"', '"').replace("\n", "\\n").replace("\r",
|
|
165
|
+
return str(s).replace("\\", "\\\\").replace("'", "\\'").replace('"', '"').replace("\n", "\\n").replace("\r",
|
|
166
|
+
"")
|
|
165
167
|
|
|
166
168
|
|
|
167
169
|
def hostname_form_html(shared_state, message, show_restart_button=False, show_skip_management=False):
|
|
@@ -200,7 +202,7 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
200
202
|
issue = hostname_issues.get(field_id)
|
|
201
203
|
timestamp = ""
|
|
202
204
|
operation = ""
|
|
203
|
-
error_details_for_modal = ""
|
|
205
|
+
error_details_for_modal = "" # New variable to hold the full error message for the modal
|
|
204
206
|
|
|
205
207
|
if not current_value:
|
|
206
208
|
status = "unset"
|
|
@@ -216,7 +218,7 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
216
218
|
status = "error"
|
|
217
219
|
status_emoji = "🔴"
|
|
218
220
|
operation = issue.get("operation", "unknown")
|
|
219
|
-
error_details_for_modal = issue.get("error", "Unknown error")
|
|
221
|
+
error_details_for_modal = issue.get("error", "Unknown error") # Get the full error message
|
|
220
222
|
timestamp = issue.get("timestamp", "")
|
|
221
223
|
status_title = f"Error in {operation}"
|
|
222
224
|
else:
|
|
@@ -290,7 +292,7 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
290
292
|
margin-top: 0.5rem;
|
|
291
293
|
font-size: 0.875rem;
|
|
292
294
|
}}
|
|
293
|
-
.import-status
|
|
295
|
+
.import-status.empty {{
|
|
294
296
|
display: none;
|
|
295
297
|
}}
|
|
296
298
|
.import-status.success {{ color: #198754; }}
|
|
@@ -367,14 +369,10 @@ def hostname_form_html(shared_state, message, show_restart_button=False, show_sk
|
|
|
367
369
|
function onHostnameFieldFocus() {{
|
|
368
370
|
var urlInput = document.getElementById('hostnamesUrl');
|
|
369
371
|
if (urlInput.value.trim() === '') {{
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
var statusDiv = document.getElementById('importStatus');
|
|
375
|
-
statusDiv.className = 'import-status';
|
|
376
|
-
statusDiv.textContent = 'Opened hostname helper in new tab. Paste the URL here after setup.';
|
|
377
|
-
}}
|
|
372
|
+
window.open('https://quasarr-host.name', '_blank');
|
|
373
|
+
var statusDiv = document.getElementById('importStatus');
|
|
374
|
+
statusDiv.className = 'import-status';
|
|
375
|
+
statusDiv.textContent = 'Opened hostname helper in new tab. Paste the URL here after setup.';
|
|
378
376
|
}}
|
|
379
377
|
}}
|
|
380
378
|
|
|
@@ -754,8 +752,26 @@ def hostnames_config(shared_state):
|
|
|
754
752
|
except Exception as e:
|
|
755
753
|
return {"success": False, "error": f"Error: {str(e)}"}
|
|
756
754
|
|
|
755
|
+
@app.get("/api/skip-login")
|
|
756
|
+
def get_skip_login():
|
|
757
|
+
"""Return list of hostnames with skipped login."""
|
|
758
|
+
response.content_type = 'application/json'
|
|
759
|
+
skip_db = DataBase("skip_login")
|
|
760
|
+
login_required_sites = ['al', 'dd', 'dl', 'nx']
|
|
761
|
+
skipped = []
|
|
762
|
+
for site in login_required_sites:
|
|
763
|
+
if skip_db.retrieve(site):
|
|
764
|
+
skipped.append(site)
|
|
765
|
+
return {"skipped": skipped}
|
|
766
|
+
|
|
767
|
+
@app.delete('/api/skip-login/<shorthand>')
|
|
768
|
+
def clear_skip_login(shorthand):
|
|
769
|
+
DataBase("skip_login").delete(shorthand)
|
|
770
|
+
return {"success": True}
|
|
771
|
+
|
|
757
772
|
info(f'Hostnames not set. Starting web server for config at: "{shared_state.values['internal_address']}".')
|
|
758
773
|
info("Please set at least one valid hostname there!")
|
|
774
|
+
quasarr.providers.web_server.temp_server_success = False
|
|
759
775
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
760
776
|
|
|
761
777
|
|
|
@@ -876,6 +892,8 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
876
892
|
password = request.forms.get('password')
|
|
877
893
|
config = Config(shorthand)
|
|
878
894
|
|
|
895
|
+
error_message = "User and Password wrong or empty!"
|
|
896
|
+
|
|
879
897
|
if user and password:
|
|
880
898
|
config.save("user", user)
|
|
881
899
|
config.save("password", password)
|
|
@@ -884,6 +902,10 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
884
902
|
DataBase("skip_login").delete(sh.lower())
|
|
885
903
|
|
|
886
904
|
if sh.lower() == "al":
|
|
905
|
+
error_message = ("User and Password wrong or empty.<br><br>"
|
|
906
|
+
"Or if you skipped Flaresolverr setup earlier, "
|
|
907
|
+
"you must chose to skip login for this site, "
|
|
908
|
+
"set up FlareSolverr in the UI and then restart Quasarr!")
|
|
887
909
|
if quasarr.providers.sessions.al.create_and_persist_session(shared_state):
|
|
888
910
|
quasarr.providers.web_server.temp_server_success = True
|
|
889
911
|
return render_reconnect_success(f"{sh} credentials set successfully")
|
|
@@ -905,13 +927,14 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
905
927
|
|
|
906
928
|
config.save("user", "")
|
|
907
929
|
config.save("password", "")
|
|
908
|
-
return render_fail(
|
|
930
|
+
return render_fail(error_message)
|
|
909
931
|
|
|
910
932
|
info(
|
|
911
933
|
f'"{shorthand.lower()}" credentials required to access download links. '
|
|
912
934
|
f'Starting web server for config at: "{shared_state.values['internal_address']}".')
|
|
913
935
|
info(f"If needed register here: 'https://{domain}'")
|
|
914
936
|
info("Please set your credentials now, or skip to allow Quasarr to launch!")
|
|
937
|
+
quasarr.providers.web_server.temp_server_success = False
|
|
915
938
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
916
939
|
|
|
917
940
|
|
|
@@ -1053,6 +1076,7 @@ def flaresolverr_config(shared_state):
|
|
|
1053
1076
|
f'Starting web server for config at: "{shared_state.values["internal_address"]}".'
|
|
1054
1077
|
)
|
|
1055
1078
|
info("Please enter your FlareSolverr URL now, or skip to allow Quasarr to launch!")
|
|
1079
|
+
quasarr.providers.web_server.temp_server_success = False
|
|
1056
1080
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
1057
1081
|
|
|
1058
1082
|
|
|
@@ -1064,7 +1088,7 @@ def jdownloader_config(shared_state):
|
|
|
1064
1088
|
@app.get('/')
|
|
1065
1089
|
def jd_form():
|
|
1066
1090
|
verify_form_html = f'''
|
|
1067
|
-
<span>If required register account at: <a href="https://my.jdownloader.org/login.html#register">
|
|
1091
|
+
<span>If required register account at: <a href="https://my.jdownloader.org/login.html#register" target="_blank">
|
|
1068
1092
|
my.jdownloader.org</a>!</span><br>
|
|
1069
1093
|
|
|
1070
1094
|
<p><strong>JDownloader must be running and connected to My JDownloader!</strong></p><br>
|
|
@@ -1125,7 +1149,7 @@ def jdownloader_config(shared_state):
|
|
|
1125
1149
|
document.getElementById("verifyButton").style.display = "none";
|
|
1126
1150
|
document.getElementById('deviceForm').style.display = 'block';
|
|
1127
1151
|
} else {
|
|
1128
|
-
showModal('Error', '
|
|
1152
|
+
showModal('Error', 'Error! Please check your Credentials.');
|
|
1129
1153
|
verifyInProgress = false;
|
|
1130
1154
|
if (btn) { btn.disabled = false; btn.textContent = 'Verify Credentials'; }
|
|
1131
1155
|
}
|
|
@@ -1188,4 +1212,5 @@ def jdownloader_config(shared_state):
|
|
|
1188
1212
|
f'Starting web server for config at: "{shared_state.values['internal_address']}".')
|
|
1189
1213
|
info("If needed register here: 'https://my.jdownloader.org/login.html#register'")
|
|
1190
1214
|
info("Please set your credentials now, to allow Quasarr to launch!")
|
|
1215
|
+
quasarr.providers.web_server.temp_server_success = False
|
|
1191
1216
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|