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