quasarr 1.3.5__py3-none-any.whl → 1.20.4__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/__init__.py +157 -56
- quasarr/api/__init__.py +141 -36
- quasarr/api/arr/__init__.py +197 -78
- quasarr/api/captcha/__init__.py +897 -42
- quasarr/api/config/__init__.py +23 -0
- quasarr/api/sponsors_helper/__init__.py +84 -22
- quasarr/api/statistics/__init__.py +196 -0
- quasarr/downloads/__init__.py +237 -434
- quasarr/downloads/linkcrypters/al.py +237 -0
- quasarr/downloads/linkcrypters/filecrypt.py +178 -31
- quasarr/downloads/linkcrypters/hide.py +123 -0
- quasarr/downloads/packages/__init__.py +461 -0
- quasarr/downloads/sources/al.py +697 -0
- quasarr/downloads/sources/by.py +106 -0
- quasarr/downloads/sources/dd.py +6 -78
- quasarr/downloads/sources/dj.py +7 -0
- quasarr/downloads/sources/dt.py +1 -1
- quasarr/downloads/sources/dw.py +2 -2
- quasarr/downloads/sources/he.py +112 -0
- quasarr/downloads/sources/mb.py +47 -0
- quasarr/downloads/sources/nk.py +51 -0
- quasarr/downloads/sources/nx.py +36 -81
- quasarr/downloads/sources/sf.py +27 -4
- quasarr/downloads/sources/sj.py +7 -0
- quasarr/downloads/sources/sl.py +90 -0
- quasarr/downloads/sources/wd.py +110 -0
- quasarr/providers/cloudflare.py +204 -0
- quasarr/providers/html_images.py +20 -0
- quasarr/providers/html_templates.py +210 -108
- quasarr/providers/imdb_metadata.py +15 -2
- quasarr/providers/myjd_api.py +36 -5
- quasarr/providers/notifications.py +30 -5
- quasarr/providers/obfuscated.py +35 -0
- quasarr/providers/sessions/__init__.py +0 -0
- quasarr/providers/sessions/al.py +286 -0
- quasarr/providers/sessions/dd.py +78 -0
- quasarr/providers/sessions/nx.py +76 -0
- quasarr/providers/shared_state.py +368 -23
- quasarr/providers/statistics.py +154 -0
- quasarr/providers/version.py +60 -1
- quasarr/search/__init__.py +112 -36
- quasarr/search/sources/al.py +448 -0
- quasarr/search/sources/by.py +203 -0
- quasarr/search/sources/dd.py +17 -6
- quasarr/search/sources/dj.py +213 -0
- quasarr/search/sources/dt.py +37 -7
- quasarr/search/sources/dw.py +27 -47
- quasarr/search/sources/fx.py +27 -29
- quasarr/search/sources/he.py +196 -0
- quasarr/search/sources/mb.py +195 -0
- quasarr/search/sources/nk.py +188 -0
- quasarr/search/sources/nx.py +22 -6
- quasarr/search/sources/sf.py +143 -151
- quasarr/search/sources/sj.py +213 -0
- quasarr/search/sources/sl.py +246 -0
- quasarr/search/sources/wd.py +208 -0
- quasarr/storage/config.py +20 -4
- quasarr/storage/setup.py +224 -56
- quasarr-1.20.4.dist-info/METADATA +304 -0
- quasarr-1.20.4.dist-info/RECORD +72 -0
- {quasarr-1.3.5.dist-info → quasarr-1.20.4.dist-info}/WHEEL +1 -1
- quasarr/providers/tvmaze_metadata.py +0 -23
- quasarr-1.3.5.dist-info/METADATA +0 -174
- quasarr-1.3.5.dist-info/RECORD +0 -43
- {quasarr-1.3.5.dist-info → quasarr-1.20.4.dist-info}/entry_points.txt +0 -0
- {quasarr-1.3.5.dist-info → quasarr-1.20.4.dist-info}/licenses/LICENSE +0 -0
- {quasarr-1.3.5.dist-info → quasarr-1.20.4.dist-info}/top_level.txt +0 -0
quasarr/storage/config.py
CHANGED
|
@@ -26,12 +26,28 @@ class Config(object):
|
|
|
26
26
|
("device", "str", ""),
|
|
27
27
|
],
|
|
28
28
|
'Hostnames': [
|
|
29
|
+
("al", "secret", ""),
|
|
30
|
+
("by", "secret", ""),
|
|
29
31
|
("dd", "secret", ""),
|
|
32
|
+
("dj", "secret", ""),
|
|
30
33
|
("dt", "secret", ""),
|
|
31
34
|
("dw", "secret", ""),
|
|
32
35
|
("fx", "secret", ""),
|
|
36
|
+
("he", "secret", ""),
|
|
37
|
+
("mb", "secret", ""),
|
|
38
|
+
("nk", "secret", ""),
|
|
33
39
|
("nx", "secret", ""),
|
|
34
|
-
("sf", "secret", "")
|
|
40
|
+
("sf", "secret", ""),
|
|
41
|
+
("sj", "secret", ""),
|
|
42
|
+
("sl", "secret", ""),
|
|
43
|
+
("wd", "secret", "")
|
|
44
|
+
],
|
|
45
|
+
'FlareSolverr': [
|
|
46
|
+
("url", "str", ""),
|
|
47
|
+
],
|
|
48
|
+
'AL': [
|
|
49
|
+
("user", "secret", ""),
|
|
50
|
+
("password", "secret", "")
|
|
35
51
|
],
|
|
36
52
|
'DD': [
|
|
37
53
|
("user", "secret", ""),
|
|
@@ -54,10 +70,10 @@ class Config(object):
|
|
|
54
70
|
self._section) or self._set_default_config(self._section)
|
|
55
71
|
self.__config__ = self._read_config(self._section)
|
|
56
72
|
except configparser.DuplicateSectionError:
|
|
57
|
-
print('
|
|
73
|
+
print('Duplicate Section in Config File')
|
|
58
74
|
raise
|
|
59
|
-
except:
|
|
60
|
-
print('
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f'Unknown error while reading config file: {e}')
|
|
61
77
|
raise
|
|
62
78
|
|
|
63
79
|
def _set_default_config(self, section):
|
quasarr/storage/setup.py
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
|
+
import requests
|
|
8
9
|
from bottle import Bottle, request
|
|
9
10
|
|
|
10
11
|
import quasarr
|
|
11
|
-
import quasarr.
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
import quasarr.providers.html_images as images
|
|
13
|
+
import quasarr.providers.sessions.al
|
|
14
|
+
import quasarr.providers.sessions.dd
|
|
15
|
+
import quasarr.providers.sessions.nx
|
|
14
16
|
from quasarr.providers.html_templates import render_button, render_form, render_success, render_fail
|
|
15
17
|
from quasarr.providers.log import info
|
|
16
18
|
from quasarr.providers.shared_state import extract_valid_hostname
|
|
@@ -27,7 +29,7 @@ def path_config(shared_state):
|
|
|
27
29
|
def config_form():
|
|
28
30
|
config_form_html = f'''
|
|
29
31
|
<form action="/api/config" method="post">
|
|
30
|
-
<label for="config_path">Path</label
|
|
32
|
+
<label for="config_path">Path</label>
|
|
31
33
|
<input type="text" id="config_path" name="config_path" placeholder="{current_path}"><br>
|
|
32
34
|
{render_button("Save", "primary", {"type": "submit"})}
|
|
33
35
|
</form>
|
|
@@ -65,54 +67,157 @@ def path_config(shared_state):
|
|
|
65
67
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
def hostname_form_html(shared_state, message):
|
|
71
|
+
hostname_fields = '''
|
|
72
|
+
<label for="{id}" style="display:inline-flex; align-items:center; gap:4px;">{label}{img_html}</label>
|
|
73
|
+
<input type="text" id="{id}" name="{id}" placeholder="example.com" autocorrect="off" autocomplete="off" value="{value}"><br>
|
|
74
|
+
'''
|
|
75
|
+
|
|
76
|
+
field_html = []
|
|
77
|
+
hostnames = Config('Hostnames') # Load once outside the loop
|
|
78
|
+
for label in shared_state.values["sites"]:
|
|
79
|
+
field_id = label.lower()
|
|
80
|
+
img_html = ''
|
|
81
|
+
try:
|
|
82
|
+
img_data = getattr(images, field_id)
|
|
83
|
+
if img_data:
|
|
84
|
+
img_html = f' <img src="{img_data}" width="16" height="16" style="filter: blur(2px);" alt="{label} icon">'
|
|
85
|
+
except AttributeError:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
# Get the current value (if any and non-empty)
|
|
89
|
+
current_value = hostnames.get(field_id)
|
|
90
|
+
if not current_value:
|
|
91
|
+
current_value = '' # Ensure it's empty if None or ""
|
|
92
|
+
|
|
93
|
+
field_html.append(hostname_fields.format(
|
|
94
|
+
id=field_id,
|
|
95
|
+
label=label,
|
|
96
|
+
img_html=img_html,
|
|
97
|
+
value=current_value
|
|
98
|
+
))
|
|
99
|
+
|
|
100
|
+
hostname_form_content = "".join(field_html)
|
|
101
|
+
button_html = render_button("Save", "primary", {"type": "submit"})
|
|
102
|
+
|
|
103
|
+
template = """
|
|
104
|
+
<div id="message" style="margin-bottom:0.5em;">{message}</div>
|
|
105
|
+
<div id="error-msg" style="color:red; margin-bottom:1em;"></div>
|
|
106
|
+
|
|
107
|
+
<form action="/api/hostnames" method="post" onsubmit="return validateHostnames(this)">
|
|
108
|
+
{hostname_form_content}
|
|
109
|
+
{button}
|
|
110
|
+
</form>
|
|
111
|
+
|
|
112
|
+
<script>
|
|
113
|
+
function validateHostnames(form) {{
|
|
114
|
+
var errorDiv = document.getElementById('error-msg');
|
|
115
|
+
errorDiv.textContent = '';
|
|
116
|
+
|
|
117
|
+
var inputs = form.querySelectorAll('input[type="text"]');
|
|
118
|
+
for (var i = 0; i < inputs.length; i++) {{
|
|
119
|
+
if (inputs[i].value.trim() !== '') {{
|
|
120
|
+
return true;
|
|
121
|
+
}}
|
|
122
|
+
}}
|
|
123
|
+
|
|
124
|
+
errorDiv.textContent = 'Please fill in at least one hostname!';
|
|
125
|
+
inputs[0].focus();
|
|
126
|
+
return false;
|
|
127
|
+
}}
|
|
128
|
+
</script>
|
|
129
|
+
"""
|
|
130
|
+
return template.format(
|
|
131
|
+
message=message,
|
|
132
|
+
hostname_form_content=hostname_form_content,
|
|
133
|
+
button=button_html
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def save_hostnames(shared_state, timeout=5, first_run=True):
|
|
138
|
+
hostnames = Config('Hostnames')
|
|
139
|
+
|
|
140
|
+
# Collect submitted hostnames, validate, and track errors
|
|
141
|
+
valid_domains = {}
|
|
142
|
+
errors = {}
|
|
143
|
+
|
|
144
|
+
for site_key in shared_state.values['sites']:
|
|
145
|
+
shorthand = site_key.lower()
|
|
146
|
+
raw_value = request.forms.get(shorthand)
|
|
147
|
+
# treat missing or empty string as intentional clear, no validation
|
|
148
|
+
if raw_value is None or raw_value.strip() == '':
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# non-empty submission: must validate
|
|
152
|
+
result = extract_valid_hostname(raw_value, shorthand)
|
|
153
|
+
domain = result.get('domain')
|
|
154
|
+
message = result.get('message', 'Error checking the hostname you provided!')
|
|
155
|
+
if domain:
|
|
156
|
+
valid_domains[site_key] = domain
|
|
157
|
+
else:
|
|
158
|
+
errors[site_key] = message
|
|
159
|
+
|
|
160
|
+
# Filter out any accidental empty domains and require at least one valid hostname overall
|
|
161
|
+
valid_domains = {k: d for k, d in valid_domains.items() if d}
|
|
162
|
+
if not valid_domains:
|
|
163
|
+
# report last or generic message
|
|
164
|
+
fail_msg = next(iter(errors.values()), 'No valid hostname provided!')
|
|
165
|
+
return render_fail(fail_msg)
|
|
166
|
+
|
|
167
|
+
# Save: valid ones, explicit empty for those omitted cleanly, leave untouched if error
|
|
168
|
+
changed_sites = []
|
|
169
|
+
for site_key in shared_state.values['sites']:
|
|
170
|
+
shorthand = site_key.lower()
|
|
171
|
+
raw_value = request.forms.get(shorthand)
|
|
172
|
+
# determine if change applies
|
|
173
|
+
if site_key in valid_domains:
|
|
174
|
+
new_val = valid_domains[site_key]
|
|
175
|
+
old_val = hostnames.get(shorthand) or ''
|
|
176
|
+
if old_val != new_val:
|
|
177
|
+
hostnames.save(shorthand, new_val)
|
|
178
|
+
changed_sites.append(shorthand)
|
|
179
|
+
elif raw_value is None:
|
|
180
|
+
# no submission: leave untouched
|
|
181
|
+
continue
|
|
182
|
+
elif raw_value.strip() == '':
|
|
183
|
+
old_val = hostnames.get(shorthand) or ''
|
|
184
|
+
if old_val != '':
|
|
185
|
+
hostnames.save(shorthand, '')
|
|
186
|
+
|
|
187
|
+
quasarr.providers.web_server.temp_server_success = True
|
|
188
|
+
|
|
189
|
+
# Build success message, include any per-site errors
|
|
190
|
+
success_msg = 'At least one valid hostname set!'
|
|
191
|
+
if errors:
|
|
192
|
+
optional_text = "<br>".join(f"{site}: {msg}" for site, msg in errors.items()) + "<br>"
|
|
193
|
+
else:
|
|
194
|
+
optional_text = "All provided hostnames are valid.<br>"
|
|
195
|
+
|
|
196
|
+
if not first_run:
|
|
197
|
+
# Append restart notice for specific sites that actually changed
|
|
198
|
+
for site in changed_sites:
|
|
199
|
+
if site.lower() in {'al', 'dd', 'nx'}:
|
|
200
|
+
optional_text += f"{site.upper()}: You must restart Quasarr and follow additional steps to start using this site.<br>"
|
|
201
|
+
|
|
202
|
+
return render_success(success_msg, timeout, optional_text=optional_text)
|
|
203
|
+
|
|
204
|
+
|
|
68
205
|
def hostnames_config(shared_state):
|
|
69
206
|
app = Bottle()
|
|
70
207
|
|
|
71
208
|
@app.get('/')
|
|
72
209
|
def hostname_form():
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
hostname_form_html = f'''
|
|
82
|
-
<form action="/api/hostnames" method="post">
|
|
83
|
-
{hostname_form_content}
|
|
84
|
-
{render_button("Save", "primary", {"type": "submit"})}
|
|
85
|
-
</form>
|
|
86
|
-
'''
|
|
87
|
-
|
|
88
|
-
return render_form("Set at least one valid hostname", hostname_form_html)
|
|
210
|
+
message = """<p>
|
|
211
|
+
If you're having trouble setting this up, take a closer look at
|
|
212
|
+
<a href="https://github.com/rix1337/Quasarr?tab=readme-ov-file#instructions" target="_blank" rel="noopener noreferrer">
|
|
213
|
+
step one of these instructions.
|
|
214
|
+
</a>
|
|
215
|
+
</p>"""
|
|
216
|
+
return render_form("Set at least one valid hostname", hostname_form_html(shared_state, message))
|
|
89
217
|
|
|
90
218
|
@app.post("/api/hostnames")
|
|
91
219
|
def set_hostnames():
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
hostname_set = False
|
|
95
|
-
|
|
96
|
-
for key in shared_state.values["sites"]:
|
|
97
|
-
shorthand = key.lower()
|
|
98
|
-
hostname = request.forms.get(shorthand)
|
|
99
|
-
try:
|
|
100
|
-
if hostname:
|
|
101
|
-
hostname = extract_valid_hostname(hostname, shorthand)
|
|
102
|
-
except Exception as e:
|
|
103
|
-
info(f"Error extracting domain from {hostname}: {e}")
|
|
104
|
-
continue
|
|
105
|
-
|
|
106
|
-
if hostname:
|
|
107
|
-
hostnames.save(key, hostname)
|
|
108
|
-
hostname_set = True
|
|
109
|
-
|
|
110
|
-
if hostname_set:
|
|
111
|
-
quasarr.providers.web_server.temp_server_success = True
|
|
112
|
-
return render_success("At least one valid hostname set",
|
|
113
|
-
5)
|
|
114
|
-
else:
|
|
115
|
-
return render_fail("No valid hostname provided!")
|
|
220
|
+
return save_hostnames(shared_state)
|
|
116
221
|
|
|
117
222
|
info(f'Hostnames not set. Starting web server for config at: "{shared_state.values['internal_address']}".')
|
|
118
223
|
info("Please set at least one valid hostname there!")
|
|
@@ -128,10 +233,10 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
128
233
|
def credentials_form():
|
|
129
234
|
form_content = f'''
|
|
130
235
|
<span>If required register account at: <a href="https://{domain}">{domain}</a>!</span><br><br>
|
|
131
|
-
<label for="user">Username</label
|
|
132
|
-
<input type="text" id="user" name="user" placeholder="
|
|
236
|
+
<label for="user">Username</label>
|
|
237
|
+
<input type="text" id="user" name="user" placeholder="User" autocorrect="off"><br>
|
|
133
238
|
|
|
134
|
-
<label for="password">Password</label
|
|
239
|
+
<label for="password">Password</label>
|
|
135
240
|
<input type="password" id="password" name="password" placeholder="Password"><br>
|
|
136
241
|
'''
|
|
137
242
|
|
|
@@ -145,7 +250,7 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
145
250
|
return render_form(f"Set User and Password for {shorthand}", form_html)
|
|
146
251
|
|
|
147
252
|
@app.post("/api/credentials/<sh>")
|
|
148
|
-
def
|
|
253
|
+
def set_credentials(sh):
|
|
149
254
|
user = request.forms.get('user')
|
|
150
255
|
password = request.forms.get('password')
|
|
151
256
|
config = Config(shorthand)
|
|
@@ -154,12 +259,16 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
154
259
|
config.save("user", user)
|
|
155
260
|
config.save("password", password)
|
|
156
261
|
|
|
262
|
+
if sh.lower() == "al":
|
|
263
|
+
if quasarr.providers.sessions.al.create_and_persist_session(shared_state):
|
|
264
|
+
quasarr.providers.web_server.temp_server_success = True
|
|
265
|
+
return render_success(f"{sh} credentials set successfully", 5)
|
|
157
266
|
if sh.lower() == "dd":
|
|
158
|
-
if dd.create_and_persist_session(shared_state):
|
|
267
|
+
if quasarr.providers.sessions.dd.create_and_persist_session(shared_state):
|
|
159
268
|
quasarr.providers.web_server.temp_server_success = True
|
|
160
269
|
return render_success(f"{sh} credentials set successfully", 5)
|
|
161
270
|
if sh.lower() == "nx":
|
|
162
|
-
if nx.create_and_persist_session(shared_state):
|
|
271
|
+
if quasarr.providers.sessions.nx.create_and_persist_session(shared_state):
|
|
163
272
|
quasarr.providers.web_server.temp_server_success = True
|
|
164
273
|
return render_success(f"{sh} credentials set successfully", 5)
|
|
165
274
|
|
|
@@ -175,31 +284,90 @@ def hostname_credentials_config(shared_state, shorthand, domain):
|
|
|
175
284
|
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
176
285
|
|
|
177
286
|
|
|
287
|
+
def flaresolverr_config(shared_state):
|
|
288
|
+
app = Bottle()
|
|
289
|
+
|
|
290
|
+
@app.get('/')
|
|
291
|
+
def url_form():
|
|
292
|
+
form_content = '''
|
|
293
|
+
<span><a href="https://github.com/FlareSolverr/FlareSolverr?tab=readme-ov-file#installation">A local instance</a>
|
|
294
|
+
must be running and reachable to Quasarr!</span><br><br>
|
|
295
|
+
<label for="url">FlareSolverr URL</label>
|
|
296
|
+
<input type="text" id="url" name="url" placeholder="http://192.168.0.1:8191/v1"><br>
|
|
297
|
+
'''
|
|
298
|
+
form_html = f'''
|
|
299
|
+
<form action="/api/flaresolverr" method="post">
|
|
300
|
+
{form_content}
|
|
301
|
+
{render_button("Save", "primary", {"type": "submit"})}
|
|
302
|
+
</form>
|
|
303
|
+
'''
|
|
304
|
+
return render_form("Set FlareSolverr URL", form_html)
|
|
305
|
+
|
|
306
|
+
@app.post('/api/flaresolverr')
|
|
307
|
+
def set_flaresolverr_url():
|
|
308
|
+
url = request.forms.get('url').strip()
|
|
309
|
+
config = Config("FlareSolverr")
|
|
310
|
+
|
|
311
|
+
if url:
|
|
312
|
+
try:
|
|
313
|
+
headers = {"Content-Type": "application/json"}
|
|
314
|
+
data = {
|
|
315
|
+
"cmd": "request.get",
|
|
316
|
+
"url": "http://www.google.com/",
|
|
317
|
+
"maxTimeout": 30000
|
|
318
|
+
}
|
|
319
|
+
response = requests.post(url, headers=headers, json=data, timeout=30)
|
|
320
|
+
if response.status_code == 200:
|
|
321
|
+
config.save("url", url)
|
|
322
|
+
print(f'Using Flaresolverr URL: "{url}"')
|
|
323
|
+
quasarr.providers.web_server.temp_server_success = True
|
|
324
|
+
return render_success("FlareSolverr URL saved successfully!", 5)
|
|
325
|
+
except requests.RequestException:
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
# on failure, clear any existing value and notify user
|
|
329
|
+
config.save("url", "")
|
|
330
|
+
return render_fail("Could not reach FlareSolverr at that URL (expected HTTP 200).")
|
|
331
|
+
|
|
332
|
+
info(
|
|
333
|
+
'"flaresolverr" URL is required for proper operation. '
|
|
334
|
+
f'Starting web server for config at: "{shared_state.values["internal_address"]}".'
|
|
335
|
+
)
|
|
336
|
+
info("Please enter your FlareSolverr URL now.")
|
|
337
|
+
return Server(app, listen='0.0.0.0', port=shared_state.values['port']).serve_temporarily()
|
|
338
|
+
|
|
339
|
+
|
|
178
340
|
def jdownloader_config(shared_state):
|
|
179
341
|
app = Bottle()
|
|
180
342
|
|
|
181
343
|
@app.get('/')
|
|
182
|
-
def
|
|
344
|
+
def jd_form():
|
|
183
345
|
verify_form_html = f'''
|
|
184
346
|
<span>If required register account at: <a href="https://my.jdownloader.org/login.html#register">
|
|
185
|
-
my.jdownloader.org</a>!</span><br
|
|
186
|
-
|
|
347
|
+
my.jdownloader.org</a>!</span><br>
|
|
348
|
+
|
|
349
|
+
<p><strong>JDownloader must be running and connected to My JDownloader!</strong></p><br>
|
|
350
|
+
|
|
187
351
|
<form id="verifyForm" action="/api/verify_jdownloader" method="post">
|
|
188
|
-
<label for="user">E-Mail</label
|
|
352
|
+
<label for="user">E-Mail</label>
|
|
189
353
|
<input type="text" id="user" name="user" placeholder="user@example.org" autocorrect="off"><br>
|
|
190
|
-
<label for="pass">Password</label
|
|
354
|
+
<label for="pass">Password</label>
|
|
191
355
|
<input type="password" id="pass" name="pass" placeholder="Password"><br>
|
|
192
356
|
{render_button("Verify Credentials",
|
|
193
357
|
"secondary",
|
|
194
358
|
{"id": "verifyButton", "type": "button", "onclick": "verifyCredentials()"})}
|
|
195
359
|
</form>
|
|
360
|
+
|
|
361
|
+
<p>Some JDownloader settings will be enforced by Quasarr on startup.</p>
|
|
362
|
+
|
|
196
363
|
<form action="/api/store_jdownloader" method="post" id="deviceForm" style="display: none;">
|
|
197
364
|
<input type="hidden" id="hiddenUser" name="user">
|
|
198
365
|
<input type="hidden" id="hiddenPass" name="pass">
|
|
199
|
-
<label for="device">JDownloader</label
|
|
366
|
+
<label for="device">JDownloader</label>
|
|
200
367
|
<select id="device" name="device"></select><br>
|
|
201
368
|
{render_button("Save", "primary", {"type": "submit"})}
|
|
202
369
|
</form>
|
|
370
|
+
<p><strong>Saving may take a while!</strong></p><br>
|
|
203
371
|
'''
|
|
204
372
|
|
|
205
373
|
verify_script = '''
|