quasarr 1.32.0__py3-none-any.whl → 2.0.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.

@@ -0,0 +1,374 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Quasarr
3
+ # Project by https://github.com/rix1337
4
+
5
+ import quasarr.providers.html_images as images
6
+ from quasarr.downloads.packages import get_packages, delete_package
7
+ from quasarr.providers import shared_state
8
+ from quasarr.providers.html_templates import render_button, render_centered_html
9
+
10
+
11
+ def setup_packages_routes(app):
12
+ @app.get('/packages/delete/<package_id>')
13
+ def delete_package_route(package_id):
14
+ success = delete_package(shared_state, package_id)
15
+
16
+ if success:
17
+ return render_centered_html(f'''
18
+ <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
19
+ <p>✅ Package deleted successfully.</p>
20
+ <p>{render_button("Back to Packages", "primary", {"onclick": "location.href='/packages'"})}</p>
21
+ ''')
22
+ else:
23
+ return render_centered_html(f'''
24
+ <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
25
+ <p>❌ Failed to delete package.</p>
26
+ <p>{render_button("Back to Packages", "secondary", {"onclick": "location.href='/packages'"})}</p>
27
+ ''')
28
+
29
+ @app.get('/packages')
30
+ def packages_status():
31
+ try:
32
+ device = shared_state.values["device"]
33
+ except KeyError:
34
+ device = None
35
+
36
+ if not device:
37
+ back_btn = render_button("Back", "secondary", {"onclick": "location.href='/'"})
38
+ return render_centered_html(f'''
39
+ <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
40
+ <p>JDownloader connection not established.</p>
41
+ <p>{back_btn}</p>
42
+ ''')
43
+
44
+ # Get packages data
45
+ downloads = get_packages(shared_state)
46
+ queue = downloads.get('queue', [])
47
+ history = downloads.get('history', [])
48
+
49
+ # Separate Quasarr packages from others
50
+ quasarr_queue = [p for p in queue if p.get('cat') != 'not_quasarr']
51
+ other_queue = [p for p in queue if p.get('cat') == 'not_quasarr']
52
+ quasarr_history = [p for p in history if p.get('category') != 'not_quasarr']
53
+ other_history = [p for p in history if p.get('category') == 'not_quasarr']
54
+
55
+ def get_category_emoji(cat):
56
+ return {'movies': '🎬', 'tv': '📺', 'docs': '📄', 'not_quasarr': '📦'}.get(cat, '📦')
57
+
58
+ def format_size(mb=None, bytes_val=None):
59
+ if bytes_val is not None:
60
+ mb = bytes_val / (1024 * 1024)
61
+ if mb is None or mb == 0:
62
+ return "? MB"
63
+ if mb < 1024:
64
+ return f"{mb:.0f} MB"
65
+ return f"{mb / 1024:.1f} GB"
66
+
67
+ def escape_js(s):
68
+ return s.replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"').replace('\n', '\\n')
69
+
70
+ def render_queue_item(item):
71
+ filename = item.get('filename', 'Unknown')
72
+ percentage = item.get('percentage', 0)
73
+ timeleft = item.get('timeleft', '??:??:??')
74
+ mb = item.get('mb', 0)
75
+ cat = item.get('cat', 'not_quasarr')
76
+ is_archive = item.get('is_archive', False)
77
+ nzo_id = item.get('nzo_id', '')
78
+
79
+ is_captcha = '[CAPTCHA' in filename
80
+ if is_captcha:
81
+ status_emoji = '🔒'
82
+ elif '[Extracting]' in filename:
83
+ status_emoji = '📦'
84
+ elif '[Paused]' in filename:
85
+ status_emoji = '⏸️'
86
+ elif '[Linkgrabber]' in filename:
87
+ status_emoji = '🔗'
88
+ else:
89
+ status_emoji = '⬇️'
90
+
91
+ display_name = filename
92
+ for prefix in ['[Downloading] ', '[Extracting] ', '[Paused] ', '[Linkgrabber] ', '[CAPTCHA not solved!] ']:
93
+ display_name = display_name.replace(prefix, '')
94
+
95
+ archive_badge = '<span class="badge archive">📁 ARCHIVE</span>' if is_archive else ''
96
+ cat_emoji = get_category_emoji(cat)
97
+ size_str = format_size(mb=mb)
98
+
99
+ # Progress bar - show "waiting..." for 0%
100
+ if percentage == 0:
101
+ progress_html = '<span class="progress-waiting"></span>'
102
+ else:
103
+ progress_html = f'<div class="progress-track"><div class="progress-fill" style="width: {percentage}%"></div></div>'
104
+
105
+ # Action buttons - CAPTCHA left, delete right
106
+ if is_captcha and nzo_id:
107
+ actions = f'''
108
+ <div class="package-actions">
109
+ <button class="btn-small primary" onclick="location.href='/captcha?package_id={nzo_id}'">🔓 Solve CAPTCHA</button>
110
+ <span class="spacer"></span>
111
+ <button class="btn-small danger" onclick="confirmDelete('{nzo_id}', '{escape_js(display_name)}')">🗑️</button>
112
+ </div>
113
+ '''
114
+ elif nzo_id:
115
+ actions = f'''
116
+ <div class="package-actions right-only">
117
+ <button class="btn-small danger" onclick="confirmDelete('{nzo_id}', '{escape_js(display_name)}')">🗑️</button>
118
+ </div>
119
+ '''
120
+ else:
121
+ actions = ''
122
+
123
+ return f'''
124
+ <div class="package-card">
125
+ <div class="package-header">
126
+ <span class="status-emoji">{status_emoji}</span>
127
+ <span class="package-name">{display_name}</span>
128
+ {archive_badge}
129
+ </div>
130
+ <div class="package-progress">
131
+ {progress_html}
132
+ <span class="progress-percent">{percentage}%</span>
133
+ </div>
134
+ <div class="package-details">
135
+ <span>⏱️ {timeleft}</span>
136
+ <span>💾 {size_str}</span>
137
+ <span>{cat_emoji} {cat}</span>
138
+ </div>
139
+ {actions}
140
+ </div>
141
+ '''
142
+
143
+ def render_history_item(item):
144
+ name = item.get('name', 'Unknown')
145
+ status = item.get('status', 'Unknown')
146
+ category = item.get('category', 'not_quasarr')
147
+ bytes_val = item.get('bytes', 0)
148
+ is_archive = item.get('is_archive')
149
+ extraction_ok = item.get('extraction_ok', False)
150
+ fail_message = item.get('fail_message', '')
151
+ nzo_id = item.get('nzo_id', '')
152
+
153
+ status_emoji = '✅' if status == 'Completed' else '❌'
154
+
155
+ archive_badge = ''
156
+ if is_archive:
157
+ archive_badge = '<span class="badge extracted">📁 EXTRACTED</span>' if extraction_ok else '<span class="badge pending">📁 NOT EXTRACTED</span>'
158
+
159
+ cat_emoji = get_category_emoji(category)
160
+ size_str = format_size(bytes_val=bytes_val)
161
+
162
+ error_html = f'<div class="package-error">⚠️ {fail_message}</div>' if fail_message else ''
163
+ error_class = 'error' if status != 'Completed' else ''
164
+
165
+ delete_btn = f'<div class="package-actions right-only"><button class="btn-small danger" onclick="confirmDelete(\'{nzo_id}\', \'{escape_js(name)}\')">🗑️</button></div>' if nzo_id else ''
166
+
167
+ return f'''
168
+ <div class="package-card {error_class}">
169
+ <div class="package-header">
170
+ <span class="status-emoji">{status_emoji}</span>
171
+ <span class="package-name">{name}</span>
172
+ {archive_badge}
173
+ </div>
174
+ <div class="package-details">
175
+ <span>💾 {size_str}</span>
176
+ <span>{cat_emoji} {category}</span>
177
+ </div>
178
+ {error_html}
179
+ {delete_btn}
180
+ </div>
181
+ '''
182
+
183
+ # Build HTML sections
184
+ queue_html = ''.join(render_queue_item(item) for item in
185
+ quasarr_queue) if quasarr_queue else '<p class="empty-message">No Quasarr packages in queue</p>'
186
+ history_html = ''.join(render_history_item(item) for item in
187
+ quasarr_history) if quasarr_history else '<p class="empty-message">No Quasarr packages in history</p>'
188
+
189
+ other_html = ''
190
+ if other_queue or other_history:
191
+ other_count = len(other_queue) + len(other_history)
192
+ other_items = ''
193
+ if other_queue:
194
+ other_items += '<h4>⏳ Queue</h4>' + ''.join(render_queue_item(item) for item in other_queue)
195
+ if other_history:
196
+ other_items += '<h4>📜 History</h4>' + ''.join(render_history_item(item) for item in other_history)
197
+ plural = 's' if other_count != 1 else ''
198
+ other_html = f'''
199
+ <div class="other-packages-section">
200
+ <details id="otherPackagesDetails">
201
+ <summary id="otherPackagesSummary">Show {other_count} non-Quasarr package{plural}</summary>
202
+ <div class="other-packages-content">{other_items}</div>
203
+ </details>
204
+ </div>
205
+ '''
206
+
207
+ queue_extra = f" + {len(other_queue)} other" if other_queue else ""
208
+ history_extra = f" + {len(other_history)} other" if other_history else ""
209
+
210
+ refresh_btn = render_button("Refresh Now", "primary", {"onclick": "location.reload()"})
211
+ back_btn = render_button("Back", "secondary", {"onclick": "location.href='/'"})
212
+
213
+ packages_html = f'''
214
+ <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
215
+ <h2>📦 Package Status</h2>
216
+ <div class="refresh-indicator"><span id="countdown">30</span>s until refresh</div>
217
+
218
+ <div class="packages-container">
219
+ <div class="section">
220
+ <h3>⏳ Queue ({len(quasarr_queue)}{queue_extra})</h3>
221
+ <div class="packages-list">{queue_html}</div>
222
+ </div>
223
+ <div class="section">
224
+ <h3>📜 History ({len(quasarr_history)}{history_extra})</h3>
225
+ <div class="packages-list">{history_html}</div>
226
+ </div>
227
+ {other_html}
228
+ </div>
229
+
230
+ <p>{refresh_btn}</p>
231
+ <p>{back_btn}</p>
232
+
233
+ <!-- Delete Confirmation Modal -->
234
+ <div id="deleteModal" class="modal">
235
+ <div class="modal-content">
236
+ <h3>⚠️ Delete Package?</h3>
237
+ <p class="modal-package-name" id="modalPackageName"></p>
238
+ <div class="modal-warning">
239
+ <strong>⛔ Warning:</strong> This will permanently delete the package AND all associated files from disk. This action cannot be undone!
240
+ </div>
241
+ <div class="modal-buttons">
242
+ <button class="btn-secondary" onclick="closeModal()">Cancel</button>
243
+ <button class="btn-danger" id="confirmDeleteBtn">🗑️ Delete Package & Files</button>
244
+ </div>
245
+ </div>
246
+ </div>
247
+
248
+ <style>
249
+ .packages-container {{ max-width: 600px; margin: 0 auto; }}
250
+ .section {{ margin: 20px 0; }}
251
+ .section h3 {{ margin-bottom: 15px; padding-bottom: 8px; border-bottom: 1px solid var(--border-color, #ddd); }}
252
+ .packages-list {{ display: flex; flex-direction: column; gap: 10px; }}
253
+
254
+ .package-card {{
255
+ background: var(--card-bg, #f8f9fa);
256
+ border: 1px solid var(--card-border, #dee2e6);
257
+ border-radius: 8px;
258
+ padding: 12px 15px;
259
+ transition: transform 0.2s, box-shadow 0.2s;
260
+ }}
261
+ .package-card:hover {{ transform: translateY(-1px); box-shadow: 0 2px 8px var(--card-shadow, rgba(0,0,0,0.1)); }}
262
+ .package-card.error {{ border-color: var(--error-border, #dc3545); background: var(--error-bg, #fff5f5); }}
263
+
264
+ .package-header {{ display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px; }}
265
+ .status-emoji {{ font-size: 1.2em; flex-shrink: 0; }}
266
+ .package-name {{ flex: 1; font-weight: 500; word-break: break-word; line-height: 1.3; }}
267
+
268
+ .badge {{ font-size: 0.75em; padding: 2px 6px; border-radius: 4px; white-space: nowrap; flex-shrink: 0; }}
269
+ .badge.archive {{ background: var(--badge-archive-bg, #e3f2fd); color: var(--badge-archive-color, #1565c0); }}
270
+ .badge.extracted {{ background: var(--badge-success-bg, #e8f5e9); color: var(--badge-success-color, #2e7d32); }}
271
+ .badge.pending {{ background: var(--badge-warning-bg, #fff3e0); color: var(--badge-warning-color, #e65100); }}
272
+
273
+ .package-progress {{ display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }}
274
+ .progress-track {{ flex: 1; height: 8px; background: var(--progress-track, #e0e0e0); border-radius: 4px; overflow: hidden; }}
275
+ .progress-fill {{ height: 100%; background: var(--progress-fill, #4caf50); border-radius: 4px; min-width: 4px; }}
276
+ .progress-waiting {{ flex: 1; color: var(--text-muted, #888); font-style: italic; font-size: 0.85em; }}
277
+ .progress-percent {{ font-weight: bold; min-width: 40px; text-align: right; font-size: 0.9em; }}
278
+
279
+ .package-details {{ display: flex; flex-wrap: wrap; gap: 15px; font-size: 0.85em; color: var(--text-muted, #666); }}
280
+ .package-error {{ margin-top: 8px; padding: 8px; background: var(--error-msg-bg, #ffebee); border-radius: 4px; font-size: 0.85em; color: var(--error-msg-color, #c62828); }}
281
+
282
+ .package-actions {{ margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-color, #eee); display: flex; gap: 8px; align-items: center; }}
283
+ .package-actions .spacer {{ flex: 1; }}
284
+ .package-actions.right-only {{ justify-content: flex-end; }}
285
+ .btn-small {{ padding: 5px 12px; font-size: 0.8em; border-radius: 4px; cursor: pointer; transition: all 0.2s; }}
286
+ .btn-small.primary {{ background: var(--btn-primary-bg, #007bff); color: white; border: none; }}
287
+ .btn-small.primary:hover {{ background: var(--btn-primary-hover, #0056b3); }}
288
+ .btn-small.danger {{ background: transparent; color: var(--btn-danger-text, #dc3545); border: 1px solid var(--btn-danger-border, #dc3545); }}
289
+ .btn-small.danger:hover {{ background: var(--btn-danger-hover-bg, #dc3545); color: white; }}
290
+
291
+ .empty-message {{ color: var(--text-muted, #888); font-style: italic; text-align: center; padding: 20px; }}
292
+ .refresh-indicator {{ text-align: center; font-size: 0.85em; color: var(--text-muted, #888); margin-bottom: 15px; }}
293
+
294
+ .other-packages-section {{ margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border-color, #ddd); }}
295
+ .other-packages-section summary {{ cursor: pointer; padding: 8px 0; color: var(--text-muted, #666); }}
296
+ .other-packages-section summary:hover {{ color: var(--link-color, #0066cc); }}
297
+ .other-packages-content {{ margin-top: 15px; }}
298
+ .other-packages-content h4 {{ margin: 15px 0 10px 0; font-size: 0.95em; color: var(--text-muted, #666); }}
299
+
300
+ /* Modal */
301
+ .modal {{ display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center; }}
302
+ .modal.show {{ display: flex; }}
303
+ .modal-content {{ background: var(--modal-bg, white); padding: 25px; border-radius: 12px; max-width: 400px; width: 90%; text-align: center; }}
304
+ .modal-content h3 {{ margin: 0 0 15px 0; color: var(--error-msg-color, #c62828); }}
305
+ .modal-package-name {{ font-weight: 500; word-break: break-word; padding: 10px; background: var(--card-bg, #f5f5f5); border-radius: 6px; margin: 10px 0; }}
306
+ .modal-warning {{ background: var(--error-msg-bg, #ffebee); color: var(--error-msg-color, #c62828); padding: 12px; border-radius: 6px; margin: 15px 0; font-size: 0.9em; text-align: left; }}
307
+ .modal-buttons {{ display: flex; gap: 10px; justify-content: center; margin-top: 20px; }}
308
+ .btn-danger {{ background: var(--btn-danger-bg, #dc3545); color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 500; }}
309
+ .btn-danger:hover {{ opacity: 0.9; }}
310
+
311
+ /* Dark mode */
312
+ @media (prefers-color-scheme: dark) {{
313
+ :root {{
314
+ --card-bg: #2d3748; --card-border: #4a5568; --card-shadow: rgba(0,0,0,0.3);
315
+ --border-color: #4a5568; --text-muted: #a0aec0;
316
+ --progress-track: #4a5568; --progress-fill: #68d391;
317
+ --error-border: #fc8181; --error-bg: #2d2d2d; --error-msg-bg: #3d2d2d; --error-msg-color: #fc8181;
318
+ --badge-archive-bg: #1a365d; --badge-archive-color: #63b3ed;
319
+ --badge-success-bg: #1c4532; --badge-success-color: #68d391;
320
+ --badge-warning-bg: #3d2d1a; --badge-warning-color: #f6ad55;
321
+ --link-color: #63b3ed; --modal-bg: #2d3748;
322
+ --btn-primary-bg: #3182ce; --btn-primary-hover: #2c5282;
323
+ --btn-danger-text: #fc8181; --btn-danger-border: #fc8181; --btn-danger-hover-bg: #e53e3e;
324
+ }}
325
+ }}
326
+ </style>
327
+
328
+ <script>
329
+ let countdown = 30;
330
+ const countdownEl = document.getElementById('countdown');
331
+ const refreshInterval = setInterval(() => {{
332
+ countdown--;
333
+ if (countdownEl) countdownEl.textContent = countdown;
334
+ if (countdown <= 0) location.reload();
335
+ }}, 1000);
336
+
337
+ // Restore collapse state from localStorage
338
+ const otherDetails = document.getElementById('otherPackagesDetails');
339
+ const otherSummary = document.getElementById('otherPackagesSummary');
340
+ if (otherDetails && otherSummary) {{
341
+ const count = otherSummary.textContent.match(/\\d+/)?.[0] || '0';
342
+ const plural = count !== '1' ? 's' : '';
343
+ if (localStorage.getItem('otherPackagesOpen') === 'true') {{
344
+ otherDetails.open = true;
345
+ otherSummary.textContent = 'Hide ' + count + ' non-Quasarr package' + plural;
346
+ }}
347
+ otherDetails.addEventListener('toggle', () => {{
348
+ localStorage.setItem('otherPackagesOpen', otherDetails.open);
349
+ otherSummary.textContent = (otherDetails.open ? 'Hide ' : 'Show ') + count + ' non-Quasarr package' + plural;
350
+ }});
351
+ }}
352
+
353
+ // Delete modal
354
+ let deletePackageId = null;
355
+ function confirmDelete(packageId, packageName) {{
356
+ deletePackageId = packageId;
357
+ document.getElementById('modalPackageName').textContent = packageName;
358
+ document.getElementById('deleteModal').classList.add('show');
359
+ clearInterval(refreshInterval);
360
+ }}
361
+ function closeModal() {{
362
+ document.getElementById('deleteModal').classList.remove('show');
363
+ deletePackageId = null;
364
+ location.reload();
365
+ }}
366
+ document.getElementById('confirmDeleteBtn').onclick = function() {{
367
+ if (deletePackageId) location.href = '/packages/delete/' + encodeURIComponent(deletePackageId);
368
+ }};
369
+ document.getElementById('deleteModal').onclick = function(e) {{ if (e.target === this) closeModal(); }};
370
+ document.addEventListener('keydown', function(e) {{ if (e.key === 'Escape') closeModal(); }});
371
+ </script>
372
+ '''
373
+
374
+ return render_centered_html(packages_html)
@@ -164,3 +164,7 @@ def setup_sponsors_helper_routes(app):
164
164
  except:
165
165
  pass
166
166
  return abort(500, "Failed")
167
+
168
+ @app.get("/sponsors_helper/api/ping/")
169
+ def get_sponsor_status():
170
+ return "pong"
@@ -232,6 +232,8 @@ def download(shared_state, request_from, title, url, mirror, size_mb, password,
232
232
  """Main download entry point."""
233
233
  category = "docs" if "lazylibrarian" in request_from.lower() else \
234
234
  "movies" if "radarr" in request_from.lower() else "tv"
235
+
236
+ # Problem, we should make this id deterministic, so same source and same request_from (radarr / sonarr, not their version!) must yield same hash
235
237
  package_id = f"Quasarr_{category}_{str(hash(title + url)).replace('-', '')}"
236
238
 
237
239
  if imdb_id and imdb_id.lower() == "none":
@@ -12,22 +12,58 @@ from quasarr.providers.log import info, debug
12
12
  from quasarr.providers.statistics import StatsHelper
13
13
 
14
14
 
15
- def unhide_links(shared_state, url):
15
+ def unhide_links(shared_state, url, session):
16
16
  try:
17
17
  links = []
18
18
 
19
- match = re.search(r"container/([a-z0-9\-]+)", url)
19
+ # Support both formats:
20
+ # - https://hide.cx/container/{id}
21
+ # - https://hide.cx/fc/Container/{id}.html
22
+ match = re.search(
23
+ r"/(?:fc/)?container/([a-z0-9A-Z\-]+)(?:\.html)?",
24
+ url,
25
+ re.IGNORECASE,
26
+ )
27
+
20
28
  if not match:
21
29
  info(f"Invalid hide.cx URL: {url}")
22
30
  return []
23
31
 
24
32
  container_id = match.group(1)
33
+ is_fc = "/fc/" in url.lower()
34
+ # resolve fc foreign ID to canonical container ID
35
+ if is_fc:
36
+ headers = {
37
+ "User-Agent": shared_state.values["user_agent"],
38
+ "Accept": "application/json",
39
+ "X-Requested-With": "XMLHttpRequest",
40
+ }
41
+
42
+ info(f"Resolving hide.cx foreign container ID: {container_id}")
43
+ resolve_url = f"https://api.hide.cx/fc/Container/{container_id}"
44
+ resp = session.get(resolve_url, headers=headers, timeout=30)
45
+
46
+ try:
47
+ resolved = resp.json()
48
+ except Exception:
49
+ debug(f"Failed to resolve foreign container {container_id}")
50
+ return []
51
+
52
+ canonical_id = resolved.get("id")
53
+ if not canonical_id:
54
+ debug(f"No canonical container ID found for {container_id}")
55
+ return []
56
+
57
+ container_id = canonical_id
58
+ debug(f"Resolved to canonical container ID: {container_id}")
59
+
60
+ headers = {'User-Agent': shared_state.values["user_agent"]}
25
61
  info(f"Fetching hide.cx container with ID: {container_id}")
26
62
 
27
63
  headers = {'User-Agent': shared_state.values["user_agent"]}
28
64
 
29
65
  container_url = f"https://api.hide.cx/containers/{container_id}"
30
- response = requests.get(container_url, headers=headers)
66
+ response = session.get(container_url, headers=headers)
31
67
  data = response.json()
32
68
 
33
69
  link_ids = [link.get("id") for link in data.get("links", []) if link.get("id")]
@@ -39,7 +75,7 @@ def unhide_links(shared_state, url):
39
75
  def fetch_link(link_id):
40
76
  debug(f"Fetching hide.cx link with ID: {link_id}")
41
77
  link_url = f"https://api.hide.cx/containers/{container_id}/links/{link_id}"
42
- link_data = requests.get(link_url, headers=headers).json()
78
+ link_data = session.get(link_url, headers=headers).json()
43
79
  return link_data.get("url")
44
80
 
45
81
  # Process links in batches of 10
@@ -103,12 +139,15 @@ def decrypt_links_if_hide(shared_state: Any, items: List[List[str]]) -> Dict[str
103
139
  resp = session.get(original_url, allow_redirects=True, timeout=10)
104
140
 
105
141
  final_url = resp.url
106
- if "hide.cx" in final_url:
142
+
143
+ # accept hide.cx even if it did not redirect
144
+ if "hide.cx" in final_url or "hide.cx" in original_url:
107
145
  debug(f"Identified hide.cx link: {final_url}")
108
146
  hide_urls.append(final_url)
109
147
  else:
110
148
  debug(f"Not a hide.cx link (skipped): {final_url}")
111
149
 
150
+
112
151
  except requests.RequestException as e:
113
152
  info(f"Error resolving URL {original_url}: {e}")
114
153
  continue
@@ -121,7 +160,7 @@ def decrypt_links_if_hide(shared_state: Any, items: List[List[str]]) -> Dict[str
121
160
  decrypted_links: List[str] = []
122
161
  for url in hide_urls:
123
162
  try:
124
- links = unhide_links(shared_state, url)
163
+ links = unhide_links(shared_state, url, session)
125
164
  if not links:
126
165
  debug(f"No links decrypted for {url}")
127
166
  continue