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/arr/__init__.py
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Quasarr
|
|
3
|
-
# Project by https://github.com/rix1337
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import re
|
|
7
|
-
import traceback
|
|
8
|
-
from base64 import urlsafe_b64decode
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
from xml.etree import ElementTree as ET
|
|
11
|
-
|
|
12
|
-
import requests
|
|
13
|
-
from bottle import Bottle, request, response
|
|
14
|
-
|
|
15
|
-
from quasarr.captcha_solver import get_filecrypt_links
|
|
16
|
-
from quasarr.downloads import download_package, delete_package, get_packages
|
|
17
|
-
from quasarr.providers import shared_state
|
|
18
|
-
from quasarr.providers.html_templates import render_button, render_centered_html
|
|
19
|
-
from quasarr.providers.obfuscated import captcha_js, captcha_values
|
|
20
|
-
from quasarr.providers.web_server import Server
|
|
21
|
-
from quasarr.search import get_search_results
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def api(shared_state_dict, shared_state_lock):
|
|
25
|
-
shared_state.set_state(shared_state_dict, shared_state_lock)
|
|
26
|
-
|
|
27
|
-
app = Bottle()
|
|
28
|
-
|
|
29
|
-
@app.get('/captcha')
|
|
30
|
-
def serve_captcha():
|
|
31
|
-
try:
|
|
32
|
-
device = shared_state.values["device"]
|
|
33
|
-
except KeyError:
|
|
34
|
-
device = None
|
|
35
|
-
if not device:
|
|
36
|
-
return render_centered_html(f'''<h1>Quasarr</h1>
|
|
37
|
-
<p>JDownloader connection not established.</p>''')
|
|
38
|
-
|
|
39
|
-
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
40
|
-
if not protected:
|
|
41
|
-
return render_centered_html(f'''<h1>Quasarr</h1>
|
|
42
|
-
<p>No protected packages found! CAPTCHA not needed.</p>''')
|
|
43
|
-
else:
|
|
44
|
-
package = protected[0]
|
|
45
|
-
package_id = package[0]
|
|
46
|
-
data = json.loads(package[1])
|
|
47
|
-
title = data["title"]
|
|
48
|
-
links = data["links"]
|
|
49
|
-
password = data["password"]
|
|
50
|
-
|
|
51
|
-
link_options = ""
|
|
52
|
-
if len(links) > 1:
|
|
53
|
-
for link in links:
|
|
54
|
-
if "filecrypt." in link[0]:
|
|
55
|
-
link_options += f'<option value="{link[0]}">{link[1]}</option>'
|
|
56
|
-
link_select = f'''<div id="mirrors-select">
|
|
57
|
-
<label for="link-select">Mirror:</label>
|
|
58
|
-
<select id="link-select">
|
|
59
|
-
{link_options}
|
|
60
|
-
</select>
|
|
61
|
-
</div>
|
|
62
|
-
<script>
|
|
63
|
-
document.getElementById("link-select").addEventListener("change", function() {{
|
|
64
|
-
var selectedLink = this.value;
|
|
65
|
-
document.getElementById("link-hidden").value = selectedLink;
|
|
66
|
-
}});
|
|
67
|
-
</script>
|
|
68
|
-
'''
|
|
69
|
-
else:
|
|
70
|
-
link_select = f'<div id="mirrors-select">Mirror: <b>{links[0][1]}</b></div>'
|
|
71
|
-
|
|
72
|
-
content = render_centered_html(r'''
|
|
73
|
-
<script type="text/javascript">
|
|
74
|
-
var api_key = "''' + captcha_values()["api_key"] + r'''";
|
|
75
|
-
var endpoint = '/' + window.location.pathname.split('/')[1] + '/' + api_key + '.html';
|
|
76
|
-
function handleToken(token) {
|
|
77
|
-
document.getElementById("puzzle-captcha").remove();
|
|
78
|
-
document.getElementById("mirrors-select").remove();
|
|
79
|
-
document.getElementById("captcha-key").innerText = 'Using result "' + token + '" to decrypt links...';
|
|
80
|
-
var link = document.getElementById("link-hidden").value;
|
|
81
|
-
fetch('/decrypt-filecrypt', {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
headers: {
|
|
84
|
-
'Content-Type': 'application/json',
|
|
85
|
-
},
|
|
86
|
-
body: JSON.stringify({
|
|
87
|
-
token: token,
|
|
88
|
-
''' + f'''package_id: '{package_id}',
|
|
89
|
-
title: '{title}',
|
|
90
|
-
link: link,
|
|
91
|
-
password: '{password}'
|
|
92
|
-
''' + '''})
|
|
93
|
-
})
|
|
94
|
-
.then(response => response.json())
|
|
95
|
-
.then(data => {
|
|
96
|
-
if (data.success) {
|
|
97
|
-
document.getElementById("captcha-key").insertAdjacentHTML('afterend',
|
|
98
|
-
'<p>Successful for: ' + data.title + '</p>');
|
|
99
|
-
} else {
|
|
100
|
-
document.getElementById("captcha-key").insertAdjacentHTML('afterend',
|
|
101
|
-
'<p>Failed. Check console for details!</p>');
|
|
102
|
-
}
|
|
103
|
-
document.getElementById("reload-button").style.display = "block";
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
''' + captcha_js() + f'''</script>
|
|
107
|
-
<div>
|
|
108
|
-
<h1>Quasarr</h1>
|
|
109
|
-
<div id="captcha-key"></div>
|
|
110
|
-
{link_select}<br><br>
|
|
111
|
-
<input type="hidden" id="link-hidden" value="{links[0][0]}" />
|
|
112
|
-
<div id="puzzle-captcha" aria-style="mobile">
|
|
113
|
-
<strong>Your adblocker prevents the captcha from loading. Disable it!</strong>
|
|
114
|
-
</div>
|
|
115
|
-
<div id="reload-button" style="display: none;">
|
|
116
|
-
{render_button("Solve another CAPTCHA", "secondary", {
|
|
117
|
-
"onclick": "location.reload()",
|
|
118
|
-
})}</div>
|
|
119
|
-
</div>
|
|
120
|
-
</html>''')
|
|
121
|
-
|
|
122
|
-
return content
|
|
123
|
-
|
|
124
|
-
@app.post('/decrypt-filecrypt')
|
|
125
|
-
def submit_token():
|
|
126
|
-
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
127
|
-
if not protected:
|
|
128
|
-
return {"success": False, "title": "No protected packages found! CAPTCHA not needed."}
|
|
129
|
-
|
|
130
|
-
download_links = []
|
|
131
|
-
|
|
132
|
-
try:
|
|
133
|
-
data = request.json
|
|
134
|
-
token = data.get('token')
|
|
135
|
-
package_id = data.get('package_id')
|
|
136
|
-
title = data.get('title')
|
|
137
|
-
link = data.get('link')
|
|
138
|
-
password = data.get('password')
|
|
139
|
-
if token:
|
|
140
|
-
print(f"Received token: {token}")
|
|
141
|
-
print(f"Decrypting links for {title}")
|
|
142
|
-
download_links = get_filecrypt_links(shared_state, token, title, link, password)
|
|
143
|
-
|
|
144
|
-
print(f"Decrypted {len(download_links)} download links for {title}")
|
|
145
|
-
|
|
146
|
-
shared_state.download_package(download_links, title, password, package_id)
|
|
147
|
-
|
|
148
|
-
shared_state.get_db("protected").delete(package_id)
|
|
149
|
-
|
|
150
|
-
except Exception as e:
|
|
151
|
-
print(f"Error decrypting: {e}")
|
|
152
|
-
|
|
153
|
-
return {"success": bool(download_links), "title": title}
|
|
154
|
-
|
|
155
|
-
@app.post('/captcha/<captcha_id>.html')
|
|
156
|
-
def proxy(captcha_id):
|
|
157
|
-
target_url = f"{captcha_values()["url"]}/captcha/{captcha_id}.html"
|
|
158
|
-
|
|
159
|
-
headers = {key: value for key, value in request.headers.items() if key != 'Host'}
|
|
160
|
-
data = request.body.read()
|
|
161
|
-
resp = requests.post(target_url, headers=headers, data=data)
|
|
162
|
-
|
|
163
|
-
response.content_type = resp.headers.get('Content-Type')
|
|
164
|
-
|
|
165
|
-
content = resp.text
|
|
166
|
-
content = re.sub(r'<script src="/(.*?)"></script>',
|
|
167
|
-
f'<script src="{captcha_values()["url"]}/\\1"></script>', content)
|
|
168
|
-
response.content_type = 'text/html'
|
|
169
|
-
return content
|
|
170
|
-
|
|
171
|
-
@app.post('/captcha/<captcha_id>.json')
|
|
172
|
-
def specific_proxy(captcha_id):
|
|
173
|
-
target_url = f"{captcha_values()["url"]}/captcha/{captcha_id}.json"
|
|
174
|
-
|
|
175
|
-
headers = {key: value for key, value in request.headers.items() if key != 'Host'}
|
|
176
|
-
data = request.body.read()
|
|
177
|
-
resp = requests.post(target_url, headers=headers, data=data)
|
|
178
|
-
|
|
179
|
-
response.content_type = resp.headers.get('Content-Type')
|
|
180
|
-
return resp.content
|
|
181
|
-
|
|
182
|
-
@app.get('/captcha/<captcha_id>/<uuid>/<filename>')
|
|
183
|
-
def captcha_proxy(captcha_id, uuid, filename):
|
|
184
|
-
new_url = f"{captcha_values()["url"]}/captcha/{captcha_id}/{uuid}/{filename}"
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
external_response = requests.get(new_url, stream=True)
|
|
188
|
-
external_response.raise_for_status()
|
|
189
|
-
response.content_type = 'image/png'
|
|
190
|
-
response.headers['Content-Disposition'] = f'inline; filename="{filename}"'
|
|
191
|
-
return external_response.iter_content(chunk_size=8192)
|
|
192
|
-
|
|
193
|
-
except requests.RequestException as e:
|
|
194
|
-
response.status = 502
|
|
195
|
-
return f"Error fetching resource: {e}"
|
|
196
|
-
|
|
197
|
-
@app.post('/captcha/<captcha_id>/check')
|
|
198
|
-
def captcha_check_proxy(captcha_id):
|
|
199
|
-
new_url = f"{captcha_values()["url"]}/captcha/{captcha_id}/check"
|
|
200
|
-
headers = {key: value for key, value in request.headers.items()}
|
|
201
|
-
|
|
202
|
-
data = request.body.read()
|
|
203
|
-
resp = requests.post(new_url, headers=headers, data=data)
|
|
204
|
-
|
|
205
|
-
response.status = resp.status_code
|
|
206
|
-
for header in resp.headers:
|
|
207
|
-
if header.lower() not in ['content-encoding', 'transfer-encoding', 'content-length', 'connection']:
|
|
208
|
-
response.set_header(header, resp.headers[header])
|
|
209
|
-
return resp.content
|
|
210
|
-
|
|
211
|
-
@app.get('/')
|
|
212
|
-
def index():
|
|
213
|
-
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
214
|
-
captcha_hint = ""
|
|
215
|
-
if protected:
|
|
216
|
-
package_count = len(protected)
|
|
217
|
-
package_text = f"Package{'s' if package_count > 1 else ''} waiting for CAPTCHA"
|
|
218
|
-
amount_info = f": {package_count}" if package_count > 1 else ""
|
|
219
|
-
button_text = f"Solve CAPTCHA{'s' if package_count > 1 else ''} here to decrypt links!"
|
|
220
|
-
|
|
221
|
-
captcha_hint = f'''
|
|
222
|
-
<h2>Links protected by CAPTCHA</h2>
|
|
223
|
-
<p>{package_text}{amount_info}</p>
|
|
224
|
-
<p>{render_button(button_text, "primary", {"onclick": "location.href='/captcha'"})}</p>
|
|
225
|
-
'''
|
|
226
|
-
|
|
227
|
-
info = f"""
|
|
228
|
-
<h1>Quasarr</h1>
|
|
229
|
-
<p>
|
|
230
|
-
<code id="current-url" style="background-color: #f0f0f0; padding: 5px; border-radius: 3px;">
|
|
231
|
-
{shared_state.values["internal_address"]}
|
|
232
|
-
</code>
|
|
233
|
-
</p>
|
|
234
|
-
<p>Use this exact URL as 'Newznab Indexer' and 'SABnzbd Download Client' in Sonarr/Radarr.<br>
|
|
235
|
-
Leave settings at default and use this API key: 'quasarr'</p>
|
|
236
|
-
{captcha_hint}
|
|
237
|
-
"""
|
|
238
|
-
return render_centered_html(info)
|
|
239
|
-
|
|
240
|
-
@app.get('/download/')
|
|
241
|
-
def fake_download_container():
|
|
242
|
-
payload = request.query.payload
|
|
243
|
-
decoded_payload = urlsafe_b64decode(payload).decode("utf-8").split("|")
|
|
244
|
-
title = decoded_payload[0]
|
|
245
|
-
url = decoded_payload[1]
|
|
246
|
-
size_mb = decoded_payload[2]
|
|
247
|
-
password = decoded_payload[3]
|
|
248
|
-
return f'<nzb><file title="{title}" url="{url}" size_mb="{size_mb}" password="{password}"/></nzb>'
|
|
249
|
-
|
|
250
|
-
@app.post('/api')
|
|
251
|
-
def download():
|
|
252
|
-
downloads = request.files.getall('name')
|
|
253
|
-
nzo_ids = []
|
|
254
|
-
for upload in downloads:
|
|
255
|
-
file_content = upload.file.read()
|
|
256
|
-
root = ET.fromstring(file_content)
|
|
257
|
-
title = root.find(".//file").attrib["title"]
|
|
258
|
-
url = root.find(".//file").attrib["url"]
|
|
259
|
-
size_mb = root.find(".//file").attrib["size_mb"]
|
|
260
|
-
password = root.find(".//file").attrib.get("password")
|
|
261
|
-
print(f"Attempting download for {title}")
|
|
262
|
-
|
|
263
|
-
request_from = request.headers.get('User-Agent')
|
|
264
|
-
|
|
265
|
-
nzo_id = download_package(shared_state, request_from, title, url, size_mb, password)
|
|
266
|
-
if nzo_id:
|
|
267
|
-
print(f"{title} added successfully!")
|
|
268
|
-
nzo_ids.append(nzo_id)
|
|
269
|
-
else:
|
|
270
|
-
print(f"{title} could not be added!")
|
|
271
|
-
|
|
272
|
-
return {
|
|
273
|
-
"status": True,
|
|
274
|
-
"nzo_ids": nzo_ids
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
@app.get('/api')
|
|
278
|
-
def fake_api():
|
|
279
|
-
api_type = 'sabnzbd' if request.query.mode and request.query.apikey else 'newznab' if request.query.t else None
|
|
280
|
-
|
|
281
|
-
if api_type == 'sabnzbd':
|
|
282
|
-
try:
|
|
283
|
-
mode = request.query.mode
|
|
284
|
-
if mode == "version":
|
|
285
|
-
return {
|
|
286
|
-
"version": "4.3.2"
|
|
287
|
-
}
|
|
288
|
-
elif mode == "get_config":
|
|
289
|
-
return {
|
|
290
|
-
"config": {
|
|
291
|
-
"misc": {
|
|
292
|
-
"quasarr": True,
|
|
293
|
-
"complete_dir": "/tmp/"
|
|
294
|
-
},
|
|
295
|
-
"categories": [
|
|
296
|
-
{
|
|
297
|
-
"name": "*",
|
|
298
|
-
"order": 0,
|
|
299
|
-
"dir": "",
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
"name": "movies",
|
|
303
|
-
"order": 1,
|
|
304
|
-
"dir": "",
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
"name": "tv",
|
|
308
|
-
"order": 2,
|
|
309
|
-
"dir": "",
|
|
310
|
-
}
|
|
311
|
-
]
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
elif mode == "fullstatus":
|
|
315
|
-
return {
|
|
316
|
-
"status": {
|
|
317
|
-
"quasarr": True
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
elif mode == "queue" or mode == "history":
|
|
321
|
-
if request.query.name and request.query.name == "delete":
|
|
322
|
-
package_id = request.query.value
|
|
323
|
-
deleted = delete_package(shared_state, package_id)
|
|
324
|
-
return {
|
|
325
|
-
"status": deleted,
|
|
326
|
-
"nzo_ids": [package_id]
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
packages = get_packages(shared_state)
|
|
330
|
-
if mode == "queue":
|
|
331
|
-
return {
|
|
332
|
-
"queue": {
|
|
333
|
-
"paused": False,
|
|
334
|
-
"slots": packages["queue"]
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
elif mode == "history":
|
|
338
|
-
return {
|
|
339
|
-
"history": {
|
|
340
|
-
"paused": False,
|
|
341
|
-
"slots": packages["history"]
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
except Exception as e:
|
|
345
|
-
print(f"Error loading packages: {e}")
|
|
346
|
-
print(traceback.format_exc())
|
|
347
|
-
return {
|
|
348
|
-
"status": False
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
elif api_type == 'newznab':
|
|
352
|
-
try:
|
|
353
|
-
mode = request.query.t
|
|
354
|
-
if mode == 'movie':
|
|
355
|
-
if request.query.imdbid:
|
|
356
|
-
imdb_id = f"tt{request.query.imdbid}"
|
|
357
|
-
else:
|
|
358
|
-
imdb_id = None
|
|
359
|
-
|
|
360
|
-
request_from = request.headers.get('User-Agent')
|
|
361
|
-
|
|
362
|
-
releases = get_search_results(shared_state, request_from, imdb_id=imdb_id)
|
|
363
|
-
|
|
364
|
-
items = ""
|
|
365
|
-
|
|
366
|
-
if not releases:
|
|
367
|
-
items += f'''
|
|
368
|
-
<item>
|
|
369
|
-
<title>No releases found</title>
|
|
370
|
-
<link></link>
|
|
371
|
-
<pubDate>{datetime.now().strftime('%a, %d %b %Y %H:%M:%S %z')}</pubDate>
|
|
372
|
-
<enclosure url="_" length="0" type="application/x-nzb"/>
|
|
373
|
-
<guid></guid>
|
|
374
|
-
<comments></comments>
|
|
375
|
-
<description></description>
|
|
376
|
-
</item>'''
|
|
377
|
-
|
|
378
|
-
for release in releases:
|
|
379
|
-
release = release["details"]
|
|
380
|
-
|
|
381
|
-
items += f'''
|
|
382
|
-
<item>
|
|
383
|
-
<title>{release["title"]}</title>
|
|
384
|
-
<guid isPermaLink="True">{release["link"]}</guid>
|
|
385
|
-
<link>{release["link"]}</link>
|
|
386
|
-
<comments>{release["source"]}</comments>
|
|
387
|
-
<pubDate>{release["date"]}</pubDate>
|
|
388
|
-
<enclosure url="{release["link"]}" length="{release["size"]}" type="application/x-nzb" />
|
|
389
|
-
</item>'''
|
|
390
|
-
|
|
391
|
-
return f'''<?xml version="1.0" encoding="UTF-8"?>
|
|
392
|
-
<rss version="2.0">
|
|
393
|
-
<channel>
|
|
394
|
-
{items}
|
|
395
|
-
</channel>
|
|
396
|
-
</rss>'''
|
|
397
|
-
elif mode == 'caps':
|
|
398
|
-
return '''<?xml version="1.0" encoding="UTF-8"?>
|
|
399
|
-
<caps>
|
|
400
|
-
<categories>
|
|
401
|
-
<category id="2000" name="Movies">
|
|
402
|
-
<subcat id="2010" name="Foreign"/>
|
|
403
|
-
<subcat id="2020" name="Other"/>
|
|
404
|
-
<subcat id="2030" name="SD"/>
|
|
405
|
-
<subcat id="2040" name="HD"/>
|
|
406
|
-
<subcat id="2050" name="BluRay"/>
|
|
407
|
-
<subcat id="2060" name="3D"/>
|
|
408
|
-
</category>
|
|
409
|
-
<category id="5000" name="TV">
|
|
410
|
-
<subcat id="5020" name="Foreign"/>
|
|
411
|
-
<subcat id="5030" name="SD"/>
|
|
412
|
-
<subcat id="5040" name="HD"/>
|
|
413
|
-
<subcat id="5050" name="Other"/>
|
|
414
|
-
<subcat id="5060" name="Sport"/>
|
|
415
|
-
</category>
|
|
416
|
-
</categories>
|
|
417
|
-
</caps>'''
|
|
418
|
-
except Exception as e:
|
|
419
|
-
print(f"Error loading search results: {e}")
|
|
420
|
-
print(traceback.format_exc())
|
|
421
|
-
return {"error": True}
|
|
422
|
-
|
|
423
|
-
Server(app, listen='0.0.0.0', port=shared_state.values["port"]).serve_forever()
|