quasarr 2.6.1__py3-none-any.whl → 2.7.1__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 +71 -61
- quasarr/api/__init__.py +1 -2
- quasarr/api/arr/__init__.py +66 -57
- quasarr/api/captcha/__init__.py +203 -154
- quasarr/downloads/__init__.py +12 -8
- quasarr/downloads/linkcrypters/al.py +4 -4
- quasarr/downloads/linkcrypters/filecrypt.py +1 -2
- quasarr/downloads/packages/__init__.py +62 -88
- quasarr/downloads/sources/al.py +3 -3
- quasarr/downloads/sources/by.py +3 -3
- quasarr/downloads/sources/he.py +8 -9
- quasarr/downloads/sources/nk.py +3 -3
- quasarr/downloads/sources/sl.py +6 -1
- quasarr/downloads/sources/wd.py +93 -37
- quasarr/downloads/sources/wx.py +11 -17
- quasarr/providers/auth.py +9 -13
- quasarr/providers/cloudflare.py +5 -4
- quasarr/providers/imdb_metadata.py +1 -3
- quasarr/providers/jd_cache.py +64 -90
- quasarr/providers/log.py +226 -8
- quasarr/providers/myjd_api.py +116 -94
- quasarr/providers/sessions/al.py +20 -22
- quasarr/providers/sessions/dd.py +1 -1
- quasarr/providers/sessions/dl.py +8 -10
- quasarr/providers/sessions/nx.py +1 -1
- quasarr/providers/shared_state.py +26 -15
- quasarr/providers/utils.py +15 -6
- quasarr/providers/version.py +1 -1
- quasarr/search/__init__.py +113 -82
- quasarr/search/sources/al.py +19 -23
- quasarr/search/sources/by.py +6 -6
- quasarr/search/sources/dd.py +8 -10
- quasarr/search/sources/dj.py +15 -18
- quasarr/search/sources/dl.py +25 -37
- quasarr/search/sources/dt.py +13 -15
- quasarr/search/sources/dw.py +24 -16
- quasarr/search/sources/fx.py +25 -11
- quasarr/search/sources/he.py +16 -14
- quasarr/search/sources/hs.py +7 -7
- quasarr/search/sources/mb.py +7 -7
- quasarr/search/sources/nk.py +24 -25
- quasarr/search/sources/nx.py +22 -15
- quasarr/search/sources/sf.py +18 -9
- quasarr/search/sources/sj.py +7 -7
- quasarr/search/sources/sl.py +26 -14
- quasarr/search/sources/wd.py +61 -31
- quasarr/search/sources/wx.py +33 -47
- quasarr/storage/config.py +1 -3
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/METADATA +4 -1
- quasarr-2.7.1.dist-info/RECORD +84 -0
- quasarr-2.6.1.dist-info/RECORD +0 -84
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/WHEEL +0 -0
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/entry_points.txt +0 -0
- {quasarr-2.6.1.dist-info → quasarr-2.7.1.dist-info}/licenses/LICENSE +0 -0
quasarr/__init__.py
CHANGED
|
@@ -15,7 +15,14 @@ import requests
|
|
|
15
15
|
import quasarr.providers.web_server
|
|
16
16
|
from quasarr.api import get_api
|
|
17
17
|
from quasarr.providers import shared_state, version
|
|
18
|
-
from quasarr.providers.log import
|
|
18
|
+
from quasarr.providers.log import (
|
|
19
|
+
crit,
|
|
20
|
+
debug,
|
|
21
|
+
error,
|
|
22
|
+
get_log_level,
|
|
23
|
+
info,
|
|
24
|
+
log_level_names,
|
|
25
|
+
)
|
|
19
26
|
from quasarr.providers.notifications import send_discord_message
|
|
20
27
|
from quasarr.providers.utils import (
|
|
21
28
|
FALLBACK_USER_AGENT,
|
|
@@ -67,26 +74,21 @@ def run():
|
|
|
67
74
|
└────────────────────────────────────┘""")
|
|
68
75
|
|
|
69
76
|
print("\n===== Recommended Services =====")
|
|
77
|
+
print('👉 Fast premium downloads: "https://linksnappy.com/?ref=397097" 👈')
|
|
70
78
|
print(
|
|
71
|
-
'
|
|
72
|
-
)
|
|
73
|
-
print(
|
|
74
|
-
'Sponsors get automated CAPTCHA solutions: "https://github.com/rix1337/Quasarr?tab=readme-ov-file#sponsorshelper"'
|
|
79
|
+
'👉 Automated CAPTCHA solutions: "https://github.com/rix1337/Quasarr?tab=readme-ov-file#sponsorshelper" 👈'
|
|
75
80
|
)
|
|
76
81
|
|
|
77
|
-
print("\n===== Startup Info =====")
|
|
78
82
|
port = int("8080")
|
|
79
83
|
config_path = ""
|
|
80
84
|
if os.environ.get("DOCKER"):
|
|
81
85
|
config_path = "/config"
|
|
82
86
|
if not arguments.internal_address:
|
|
83
|
-
|
|
87
|
+
error(
|
|
84
88
|
"You must set the INTERNAL_ADDRESS variable to a locally reachable URL, e.g. http://192.168.1.1:8080"
|
|
89
|
+
+ " The local URL will be used by Radarr/Sonarr to connect to Quasarr"
|
|
90
|
+
+ " Stopping Quasarr..."
|
|
85
91
|
)
|
|
86
|
-
print(
|
|
87
|
-
"The local URL will be used by Radarr/Sonarr to connect to Quasarr"
|
|
88
|
-
)
|
|
89
|
-
print("Stopping Quasarr...")
|
|
90
92
|
sys.exit(1)
|
|
91
93
|
else:
|
|
92
94
|
if arguments.port:
|
|
@@ -118,7 +120,7 @@ def run():
|
|
|
118
120
|
temp_file = tempfile.TemporaryFile(dir=config_path)
|
|
119
121
|
temp_file.close()
|
|
120
122
|
except Exception as e:
|
|
121
|
-
|
|
123
|
+
error(f'Could not access "{config_path}": {e}"Stopping Quasarr...')
|
|
122
124
|
sys.exit(1)
|
|
123
125
|
|
|
124
126
|
shared_state.set_files(config_path)
|
|
@@ -130,16 +132,13 @@ def run():
|
|
|
130
132
|
shared_state.update("user_agent", FALLBACK_USER_AGENT)
|
|
131
133
|
shared_state.update("helper_active", False)
|
|
132
134
|
|
|
133
|
-
print(f'Config path: "{config_path}"')
|
|
134
|
-
|
|
135
|
-
print("\n===== Hostnames =====")
|
|
136
135
|
try:
|
|
137
136
|
if arguments.hostnames:
|
|
138
137
|
hostnames_link = arguments.hostnames
|
|
139
138
|
if is_valid_url(hostnames_link):
|
|
140
139
|
# Store the hostnames URL for later use in web UI
|
|
141
140
|
Config("Settings").save("hostnames_url", hostnames_link)
|
|
142
|
-
|
|
141
|
+
info(f"Extracting hostnames from {hostnames_link}...")
|
|
143
142
|
allowed_keys = supported_hostnames
|
|
144
143
|
max_keys = len(allowed_keys)
|
|
145
144
|
shorthand_list = (
|
|
@@ -147,7 +146,7 @@ def run():
|
|
|
147
146
|
+ " and "
|
|
148
147
|
+ f'"{allowed_keys[-1]}"'
|
|
149
148
|
)
|
|
150
|
-
|
|
149
|
+
info(
|
|
151
150
|
f"There are up to {max_keys} hostnames currently supported: {shorthand_list}"
|
|
152
151
|
)
|
|
153
152
|
data = requests.get(hostnames_link).text
|
|
@@ -165,36 +164,32 @@ def run():
|
|
|
165
164
|
if valid_domain:
|
|
166
165
|
hostnames.save(shorthand, hostname)
|
|
167
166
|
extracted_hostnames += 1
|
|
168
|
-
|
|
167
|
+
info(
|
|
169
168
|
f'Hostname for "{shorthand}" successfully set to "{hostname}"'
|
|
170
169
|
)
|
|
171
170
|
else:
|
|
172
|
-
|
|
171
|
+
info(
|
|
173
172
|
f'Skipping invalid hostname for "{shorthand}" ("{hostname}")'
|
|
174
173
|
)
|
|
175
174
|
if extracted_hostnames == max_keys:
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
info(f"All {max_keys} hostnames successfully extracted!")
|
|
176
|
+
info(
|
|
178
177
|
"You can now remove the hostnames link from the command line / environment variable."
|
|
179
178
|
)
|
|
180
179
|
else:
|
|
181
|
-
|
|
180
|
+
info(
|
|
182
181
|
f'No Hostnames found at "{hostnames_link}". '
|
|
183
182
|
"Ensure to pass a plain hostnames list, not html or json!"
|
|
184
183
|
)
|
|
185
184
|
else:
|
|
186
|
-
|
|
185
|
+
error(f'Invalid hostnames URL: "{hostnames_link}"')
|
|
187
186
|
except Exception as e:
|
|
188
|
-
|
|
187
|
+
error(f'Error parsing hostnames link: "{e}"')
|
|
189
188
|
|
|
190
189
|
hostnames = get_clean_hostnames(shared_state)
|
|
191
190
|
if not hostnames:
|
|
192
191
|
hostnames_config(shared_state)
|
|
193
192
|
hostnames = get_clean_hostnames(shared_state)
|
|
194
|
-
print(
|
|
195
|
-
f"You have [{len(hostnames)} of {len(Config._DEFAULT_CONFIG['Hostnames'])}] supported hostnames set up"
|
|
196
|
-
)
|
|
197
|
-
print("For efficiency it is recommended to set up as few hostnames as needed.")
|
|
198
193
|
|
|
199
194
|
# Check credentials for login-required hostnames
|
|
200
195
|
skip_login_db = DataBase("skip_login")
|
|
@@ -237,43 +232,54 @@ def run():
|
|
|
237
232
|
if not user or not password or not device:
|
|
238
233
|
jdownloader_config(shared_state)
|
|
239
234
|
|
|
240
|
-
print("\n===== Notifications =====")
|
|
241
235
|
discord_url = ""
|
|
242
236
|
if arguments.discord:
|
|
243
237
|
discord_webhook_pattern = r"^https://discord\.com/api/webhooks/\d+/[\w-]+$"
|
|
244
238
|
if re.match(discord_webhook_pattern, arguments.discord):
|
|
245
239
|
shared_state.update("webhook", arguments.discord)
|
|
246
|
-
print("Using Discord Webhook URL for notifications.")
|
|
247
240
|
discord_url = arguments.discord
|
|
248
241
|
else:
|
|
249
|
-
|
|
250
|
-
else:
|
|
251
|
-
print("No Discord Webhook URL provided")
|
|
242
|
+
error(f"Invalid Discord Webhook URL provided: {arguments.discord}")
|
|
252
243
|
shared_state.update("discord", discord_url)
|
|
253
244
|
|
|
254
|
-
print("\n===== API Information =====")
|
|
255
245
|
api_key = Config("API").get("key")
|
|
256
246
|
if not api_key:
|
|
257
247
|
api_key = shared_state.generate_api_key()
|
|
248
|
+
info("API-Key generated: <g>" + api_key + "</g>")
|
|
258
249
|
|
|
259
|
-
print(
|
|
260
|
-
'Setup instructions: "https://github.com/rix1337/Quasarr?tab=readme-ov-file#instructions"'
|
|
261
|
-
)
|
|
262
|
-
print(f'URL: "{shared_state.values["internal_address"]}"')
|
|
263
|
-
print(f'API Key: "{api_key}" (without quotes)')
|
|
250
|
+
print(f"\n===== Quasarr {log_level_names[get_log_level()]} Log =====")
|
|
264
251
|
|
|
265
|
-
|
|
266
|
-
|
|
252
|
+
# Start Logging
|
|
253
|
+
info(f"Web UI: <blue>{shared_state.values['external_address']}</blue>")
|
|
254
|
+
debug(f'Config path: "{config_path}"')
|
|
267
255
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
256
|
+
# Hostnames log
|
|
257
|
+
hostnames_log = []
|
|
258
|
+
set_hostnames_count = 0
|
|
259
|
+
for key in supported_hostnames:
|
|
260
|
+
if key in hostnames:
|
|
261
|
+
hostnames_log.append(
|
|
262
|
+
f"<bg green><black>{key.upper()}</black></bg green>"
|
|
263
|
+
)
|
|
264
|
+
set_hostnames_count += 1
|
|
265
|
+
else:
|
|
266
|
+
hostnames_log.append(
|
|
267
|
+
f"<bg black><white>{key.upper()}</white></bg black>"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
total_hostnames_count = len(supported_hostnames)
|
|
271
|
+
if set_hostnames_count == total_hostnames_count:
|
|
272
|
+
count_str = f"<g>{set_hostnames_count}</g>/<g>{total_hostnames_count}</g>"
|
|
273
|
+
else:
|
|
274
|
+
count_str = f"<y>{set_hostnames_count}</y>/<g>{total_hostnames_count}</g>"
|
|
275
|
+
|
|
276
|
+
info(f"Hostnames: [{' '.join(hostnames_log)}] {count_str} set")
|
|
271
277
|
|
|
272
278
|
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
273
279
|
if protected:
|
|
274
280
|
package_count = len(protected)
|
|
275
281
|
info(
|
|
276
|
-
f"CAPTCHA-Solution required for {package_count} package{'s' if package_count > 1 else ''} at: "
|
|
282
|
+
f"CAPTCHA-Solution required for <y>{package_count}</y> package{'s' if package_count > 1 else ''} at: "
|
|
277
283
|
f'"{shared_state.values["external_address"]}/captcha"!'
|
|
278
284
|
)
|
|
279
285
|
|
|
@@ -329,21 +335,26 @@ def flaresolverr_checker(shared_state_dict, shared_state_lock):
|
|
|
329
335
|
"Some sites (AL) will not work without FlareSolverr. Configure it later in the web UI."
|
|
330
336
|
)
|
|
331
337
|
elif flaresolverr_url:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
338
|
+
debug(f"Checking FlareSolverr at URL: <blue>{flaresolverr_url}</blue>")
|
|
339
|
+
flaresolverr_version_checked = check_flaresolverr(
|
|
340
|
+
shared_state, flaresolverr_url
|
|
341
|
+
)
|
|
342
|
+
if flaresolverr_version_checked:
|
|
335
343
|
info(
|
|
336
|
-
f
|
|
344
|
+
f"FlareSolverr connection successful: <g>v.{flaresolverr_version_checked}</g>"
|
|
345
|
+
)
|
|
346
|
+
debug(
|
|
347
|
+
f"Using Flaresolverr's User-Agent: <g>{shared_state.values['user_agent']}</g>"
|
|
337
348
|
)
|
|
338
349
|
else:
|
|
339
|
-
|
|
350
|
+
error("FlareSolverr check failed - using fallback user agent")
|
|
340
351
|
# Fallback user agent is already set in main process, but we log it
|
|
341
352
|
info(f'User Agent (fallback): "{FALLBACK_USER_AGENT}"')
|
|
342
353
|
|
|
343
354
|
except KeyboardInterrupt:
|
|
344
355
|
pass
|
|
345
356
|
except Exception as e:
|
|
346
|
-
|
|
357
|
+
error(f"An unexpected error occurred in FlareSolverr checker: {e}")
|
|
347
358
|
|
|
348
359
|
|
|
349
360
|
def update_checker(shared_state_dict, shared_state_lock):
|
|
@@ -359,8 +370,9 @@ def update_checker(shared_state_dict, shared_state_lock):
|
|
|
359
370
|
try:
|
|
360
371
|
update_available = version.newer_version_available()
|
|
361
372
|
except Exception as e:
|
|
362
|
-
|
|
363
|
-
|
|
373
|
+
error(
|
|
374
|
+
f"Error getting latest version: {e}!\nPlease manually check: <blue>{link}</blue> for more information!"
|
|
375
|
+
)
|
|
364
376
|
update_available = None
|
|
365
377
|
|
|
366
378
|
if (
|
|
@@ -392,27 +404,25 @@ def jdownloader_connection(shared_state_dict, shared_state_lock):
|
|
|
392
404
|
device = shared_state.get_device()
|
|
393
405
|
|
|
394
406
|
try:
|
|
395
|
-
info(
|
|
396
|
-
f'Connection to JDownloader successful. Device name: "{device.name}"'
|
|
397
|
-
)
|
|
407
|
+
info(f"Connection to JDownloader successful: <g>{device.name}</g>")
|
|
398
408
|
except Exception as e:
|
|
399
|
-
|
|
409
|
+
crit(f"Error connecting to JDownloader: {e}! Stopping Quasarr...")
|
|
400
410
|
sys.exit(1)
|
|
401
411
|
|
|
402
412
|
try:
|
|
403
413
|
shared_state.set_device_settings()
|
|
404
414
|
except Exception as e:
|
|
405
|
-
|
|
415
|
+
error(f"Error checking settings: {e}")
|
|
406
416
|
|
|
407
417
|
try:
|
|
408
418
|
shared_state.update_jdownloader()
|
|
409
419
|
except Exception as e:
|
|
410
|
-
|
|
420
|
+
error(f"Error updating JDownloader: {e}")
|
|
411
421
|
|
|
412
422
|
try:
|
|
413
423
|
shared_state.start_downloads()
|
|
414
424
|
except Exception as e:
|
|
415
|
-
|
|
425
|
+
error(f"Error starting downloads: {e}")
|
|
416
426
|
|
|
417
427
|
while True:
|
|
418
428
|
time.sleep(300)
|
|
@@ -420,7 +430,7 @@ def jdownloader_connection(shared_state_dict, shared_state_lock):
|
|
|
420
430
|
shared_state.values.get("device")
|
|
421
431
|
)
|
|
422
432
|
if not device_state:
|
|
423
|
-
|
|
433
|
+
error("Lost connection to JDownloader. Reconnecting...")
|
|
424
434
|
shared_state.update("device", False)
|
|
425
435
|
break
|
|
426
436
|
|
quasarr/api/__init__.py
CHANGED
|
@@ -34,8 +34,7 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
34
34
|
add_auth_routes(app)
|
|
35
35
|
add_auth_hook(
|
|
36
36
|
app,
|
|
37
|
-
|
|
38
|
-
whitelist_suffixes=[".user.js"],
|
|
37
|
+
whitelist=["/api", "/api/", "/sponsors_helper/", "/download/", ".user.js"],
|
|
39
38
|
)
|
|
40
39
|
|
|
41
40
|
setup_arr_routes(app)
|
quasarr/api/arr/__init__.py
CHANGED
|
@@ -59,7 +59,8 @@ def setup_arr_routes(app):
|
|
|
59
59
|
def check_user_agent():
|
|
60
60
|
user_agent = request.headers.get("User-Agent") or ""
|
|
61
61
|
if not any(
|
|
62
|
-
tool in user_agent.lower()
|
|
62
|
+
tool in user_agent.lower()
|
|
63
|
+
for tool in ["radarr", "sonarr", "lazylibrarian", "python-requests"]
|
|
63
64
|
):
|
|
64
65
|
msg = f"Unsupported User-Agent: {user_agent}. Quasarr as a compatibility layer must be called by Radarr, Sonarr or LazyLibrarian directly."
|
|
65
66
|
info(msg)
|
|
@@ -107,7 +108,7 @@ def setup_arr_routes(app):
|
|
|
107
108
|
imdb_id = root.find(".//file").attrib.get("imdb_id")
|
|
108
109
|
source_key = root.find(".//file").attrib.get("source_key") or None
|
|
109
110
|
|
|
110
|
-
info(f
|
|
111
|
+
info(f"Attempting download for <y>{title}</y>")
|
|
111
112
|
downloaded = download(
|
|
112
113
|
shared_state,
|
|
113
114
|
request_from,
|
|
@@ -125,12 +126,12 @@ def setup_arr_routes(app):
|
|
|
125
126
|
title = downloaded["title"]
|
|
126
127
|
|
|
127
128
|
if success:
|
|
128
|
-
info(f
|
|
129
|
+
info(f"<y>{title}</y> added successfully!")
|
|
129
130
|
else:
|
|
130
|
-
info(f
|
|
131
|
+
info(f"<y>{title}</y> added unsuccessfully! See log for details.")
|
|
131
132
|
nzo_ids.append(package_id)
|
|
132
133
|
except KeyError:
|
|
133
|
-
info(f
|
|
134
|
+
info(f"Failed to download <y>{title}</y> - no package_id returned")
|
|
134
135
|
|
|
135
136
|
return {"status": True, "nzo_ids": nzo_ids}
|
|
136
137
|
|
|
@@ -209,7 +210,7 @@ def setup_arr_routes(app):
|
|
|
209
210
|
abort(400, f"invalid payload format: {e}")
|
|
210
211
|
|
|
211
212
|
nzo_ids = []
|
|
212
|
-
info(f
|
|
213
|
+
info(f"Attempting download for <y>{parsed_payload['title']}</y>")
|
|
213
214
|
|
|
214
215
|
downloaded = download(
|
|
215
216
|
shared_state,
|
|
@@ -229,11 +230,9 @@ def setup_arr_routes(app):
|
|
|
229
230
|
title = downloaded.get("title", parsed_payload["title"])
|
|
230
231
|
|
|
231
232
|
if success:
|
|
232
|
-
info(f'"{title}
|
|
233
|
+
info(f'"{title} added successfully!')
|
|
233
234
|
else:
|
|
234
|
-
info(
|
|
235
|
-
f'"{title}" added unsuccessfully! See log for details.'
|
|
236
|
-
)
|
|
235
|
+
info(f'"{title} added unsuccessfully! See log for details.')
|
|
237
236
|
nzo_ids.append(package_id)
|
|
238
237
|
except KeyError:
|
|
239
238
|
info(
|
|
@@ -306,66 +305,76 @@ def setup_arr_routes(app):
|
|
|
306
305
|
releases = []
|
|
307
306
|
|
|
308
307
|
try:
|
|
309
|
-
offset = int(getattr(request.query, "offset", 0))
|
|
310
|
-
except (AttributeError, ValueError):
|
|
308
|
+
offset = int(getattr(request.query, "offset", 0) or 0)
|
|
309
|
+
except (AttributeError, ValueError) as e:
|
|
310
|
+
debug(f"Error parsing offset parameter: {e}")
|
|
311
311
|
offset = 0
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
try:
|
|
314
|
+
limit = int(getattr(request.query, "limit", 9999) or 9999)
|
|
315
|
+
except (AttributeError, ValueError) as e:
|
|
316
|
+
debug(f"Error parsing limit parameter: {e}")
|
|
317
|
+
limit = 1000
|
|
318
|
+
|
|
319
|
+
if mode == "movie":
|
|
320
|
+
# supported params: imdbid
|
|
321
|
+
imdb_id = getattr(request.query, "imdbid", "")
|
|
322
|
+
releases = get_search_results(
|
|
323
|
+
shared_state,
|
|
324
|
+
request_from,
|
|
325
|
+
imdb_id=imdb_id,
|
|
326
|
+
mirror=mirror,
|
|
327
|
+
offset=offset,
|
|
328
|
+
limit=limit,
|
|
316
329
|
)
|
|
317
330
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
331
|
+
elif mode == "tvsearch":
|
|
332
|
+
# supported params: imdbid, season, ep
|
|
333
|
+
imdb_id = getattr(request.query, "imdbid", "")
|
|
334
|
+
season = getattr(request.query, "season", None)
|
|
335
|
+
episode = getattr(request.query, "ep", None)
|
|
336
|
+
releases = get_search_results(
|
|
337
|
+
shared_state,
|
|
338
|
+
request_from,
|
|
339
|
+
imdb_id=imdb_id,
|
|
340
|
+
mirror=mirror,
|
|
341
|
+
season=season,
|
|
342
|
+
episode=episode,
|
|
343
|
+
offset=offset,
|
|
344
|
+
limit=limit,
|
|
345
|
+
)
|
|
322
346
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
347
|
+
elif mode == "book":
|
|
348
|
+
author = getattr(request.query, "author", "")
|
|
349
|
+
title = getattr(request.query, "title", "")
|
|
350
|
+
search_phrase = " ".join(filter(None, [author, title]))
|
|
351
|
+
releases = get_search_results(
|
|
352
|
+
shared_state,
|
|
353
|
+
request_from,
|
|
354
|
+
search_phrase=search_phrase,
|
|
355
|
+
mirror=mirror,
|
|
356
|
+
offset=offset,
|
|
357
|
+
limit=limit,
|
|
358
|
+
)
|
|
329
359
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
season = getattr(request.query, "season", None)
|
|
334
|
-
episode = getattr(request.query, "ep", None)
|
|
335
|
-
releases = get_search_results(
|
|
336
|
-
shared_state,
|
|
337
|
-
request_from,
|
|
338
|
-
imdb_id=imdb_id,
|
|
339
|
-
mirror=mirror,
|
|
340
|
-
season=season,
|
|
341
|
-
episode=episode,
|
|
342
|
-
)
|
|
343
|
-
elif mode == "book":
|
|
344
|
-
author = getattr(request.query, "author", "")
|
|
345
|
-
title = getattr(request.query, "title", "")
|
|
346
|
-
search_phrase = " ".join(filter(None, [author, title]))
|
|
360
|
+
elif mode == "search":
|
|
361
|
+
if "lazylibrarian" in request_from.lower():
|
|
362
|
+
search_phrase = getattr(request.query, "q", "")
|
|
347
363
|
releases = get_search_results(
|
|
348
364
|
shared_state,
|
|
349
365
|
request_from,
|
|
350
366
|
search_phrase=search_phrase,
|
|
351
367
|
mirror=mirror,
|
|
368
|
+
offset=offset,
|
|
369
|
+
limit=limit,
|
|
370
|
+
)
|
|
371
|
+
else:
|
|
372
|
+
# sonarr expects this but we will not support non-imdbid searches
|
|
373
|
+
debug(
|
|
374
|
+
f"Ignoring search request from {request_from} - only imdbid searches are supported"
|
|
352
375
|
)
|
|
353
376
|
|
|
354
|
-
|
|
355
|
-
if "lazylibrarian" in request_from.lower():
|
|
356
|
-
search_phrase = getattr(request.query, "q", "")
|
|
357
|
-
releases = get_search_results(
|
|
358
|
-
shared_state,
|
|
359
|
-
request_from,
|
|
360
|
-
search_phrase=search_phrase,
|
|
361
|
-
mirror=mirror,
|
|
362
|
-
)
|
|
363
|
-
else:
|
|
364
|
-
# sonarr expects this but we will not support non-imdbid searches
|
|
365
|
-
debug(
|
|
366
|
-
f"Ignoring search request from {request_from} - only imdbid searches are supported"
|
|
367
|
-
)
|
|
368
|
-
|
|
377
|
+
# XML Generation (releases are already sliced)
|
|
369
378
|
items = ""
|
|
370
379
|
for release in releases:
|
|
371
380
|
release = release.get("details", {})
|