quasarr 1.19.0__py3-none-any.whl → 1.20.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.

@@ -16,7 +16,7 @@ from quasarr.downloads.packages import delete_package
16
16
  from quasarr.providers import shared_state
17
17
  from quasarr.providers.html_templates import render_button, render_centered_html
18
18
  from quasarr.providers.log import info, debug
19
- from quasarr.providers.obfuscated import captcha_js, captcha_values, filecrypt_quasarr_helper_user_js
19
+ from quasarr.providers.obfuscated import captcha_js, captcha_values, filecrypt_user_js, junkies_user_js
20
20
  from quasarr.providers.statistics import StatsHelper
21
21
 
22
22
 
@@ -58,7 +58,7 @@ def setup_captcha_routes(app):
58
58
  desired_mirror = None
59
59
 
60
60
  # This is set for circle CAPTCHAs
61
- session = data.get("session", None)
61
+ filecrypt_session = data.get("session", None)
62
62
 
63
63
  # This is required for cutcaptcha
64
64
  rapid = [ln for ln in links if "rapidgator" in ln[1].lower()]
@@ -73,15 +73,21 @@ def setup_captcha_routes(app):
73
73
  "title": title,
74
74
  "password": password,
75
75
  "mirror": desired_mirror,
76
- "session": session,
76
+ "session": filecrypt_session,
77
77
  "links": prioritized_links,
78
78
  "original_url": original_url
79
79
  }
80
80
 
81
81
  encoded_payload = urlsafe_b64encode(json.dumps(payload).encode()).decode()
82
82
 
83
- if session:
84
- debug(f'Session "{session}" found, redirecting to circle CAPTCHA')
83
+ sj = True
84
+ dj = False
85
+
86
+ if sj or dj:
87
+ debug("Redirecting to Junkies CAPTCHA")
88
+ redirect(f"/captcha/junkies?data={quote(encoded_payload)}")
89
+ elif filecrypt_session:
90
+ debug(f'Redirecting to circle CAPTCHA')
85
91
  redirect(f"/captcha/circle?data={quote(encoded_payload)}")
86
92
  else:
87
93
  debug(f"Redirecting to cutcaptcha")
@@ -101,13 +107,140 @@ def setup_captcha_routes(app):
101
107
  except Exception as e:
102
108
  return {"error": f"Failed to decode payload: {str(e)}"}
103
109
 
104
- @app.get('/captcha/quasarr.user.js')
105
- def serve_quasarr_user_js():
106
- content = filecrypt_quasarr_helper_user_js()
110
+ @app.get("/captcha/junkies")
111
+ def serve_junkies_captcha():
112
+ payload = decode_payload()
113
+
114
+ if "error" in payload:
115
+ return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
116
+ <p>{payload["error"]}</p>
117
+ <p>
118
+ {render_button("Back", "secondary", {"onclick": "location.href='/'"})}
119
+ </p>''')
120
+
121
+ package_id = payload.get("package_id")
122
+ title = payload.get("title")
123
+ password = payload.get("password")
124
+ urls = payload.get("links")
125
+ url = urls[0]
126
+
127
+ return render_centered_html(f"""
128
+ <!DOCTYPE html>
129
+ <html>
130
+ <body>
131
+ <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
132
+ <p><b>Package:</b> {title}</p>
133
+ {render_junkies_section(url, package_id, title, password)}
134
+ <p>
135
+ {render_button("Delete Package", "secondary", {"onclick": f"location.href='/captcha/delete/{package_id}'"})}
136
+ </p>
137
+ <p>
138
+ {render_button("Back", "secondary", {"onclick": "location.href='/'"})}
139
+ </p>
140
+
141
+ </body>
142
+ </html>""")
143
+
144
+ def render_junkies_section(url, package_id, title, password):
145
+ """Render the UI section for SJ and DJ pages"""
146
+
147
+ # Generate userscript URL with transfer params
148
+ # Get base URL of current request
149
+ base_url = request.urlparts.scheme + '://' + request.urlparts.netloc
150
+ transfer_url = f"{base_url}/captcha/quick-transfer"
151
+
152
+ url_with_quick_transfer_params = (
153
+ f"{url}?"
154
+ f"transfer_url={quote(transfer_url)}&"
155
+ f"pkg_id={quote(package_id)}&"
156
+ f"pkg_title={quote(title)}&"
157
+ f"pkg_pass={quote(password)}"
158
+ )
159
+
160
+ return f'''
161
+ <div>
162
+ <!-- One-time setup section - visually separated -->
163
+ <div id="setup-instructions" style="background: #2a2a2a; border: 2px solid #444; border-radius: 8px; padding: 16px; margin-bottom: 24px;">
164
+ <h3 style="margin-top: 0; color: #58a6ff;">First Time Setup:</h3>
165
+ <p style="margin-bottom: 8px;">
166
+ <a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. Install Tampermonkey</a>
167
+ </p>
168
+ <p style="margin-top: 0; margin-bottom: 12px;">
169
+ <a href="/captcha/junkies.user.js" target="_blank">2. Install this userscript</a>
170
+ </p>
171
+ <p style="margin-top: 0;">
172
+ <button id="hide-setup-btn" type="button" style="background: #444; color: #fff; border: 1px solid #666; padding: 6px 12px; border-radius: 4px; cursor: pointer;">
173
+ ✅ Don't show this again
174
+ </button>
175
+ </p>
176
+ </div>
177
+
178
+ <!-- Hidden "show instructions" link -->
179
+ <div id="show-instructions-link" style="display: none; margin-bottom: 16px;">
180
+ <a href="#" id="show-setup-btn" style="color: #58a6ff;">ℹ️ Show instructions again</a>
181
+ </div>
182
+
183
+ <strong><a href="{url_with_quick_transfer_params}" target="_blank">🔗 Obtain the download links here!</a></strong><br><br>
184
+
185
+ <form id="bypass-form" action="/captcha/bypass-submit" method="post" enctype="multipart/form-data">
186
+ <input type="hidden" name="package_id" value="{package_id}" />
187
+ <input type="hidden" name="title" value="{title}" />
188
+ <input type="hidden" name="password" value="{password}" />
189
+
190
+ <div>
191
+ <strong>Paste the download links (one per line):</strong>
192
+ <textarea id="links-input" name="links" rows="5" style="width: 100%; padding: 8px; font-family: monospace; resize: vertical;"></textarea>
193
+ </div>
194
+
195
+ <div>
196
+ {render_button("Submit", "primary", {"type": "submit"})}
197
+ </div>
198
+ </form>
199
+ </div>
200
+ <script>
201
+ // Handle setup instructions hide/show
202
+ const hideSetup = localStorage.getItem('hideJunkiesSetupInstructions');
203
+ const setupBox = document.getElementById('setup-instructions');
204
+ const showLink = document.getElementById('show-instructions-link');
205
+
206
+ if (hideSetup === 'true') {{
207
+ setupBox.style.display = 'none';
208
+ showLink.style.display = 'block';
209
+ }}
210
+
211
+ // Hide setup instructions
212
+ document.getElementById('hide-setup-btn').addEventListener('click', function() {{
213
+ localStorage.setItem('hideJunkiesSetupInstructions', 'true');
214
+ setupBox.style.display = 'none';
215
+ showLink.style.display = 'block';
216
+ }});
217
+
218
+ // Show setup instructions again
219
+ document.getElementById('show-setup-btn').addEventListener('click', function(e) {{
220
+ e.preventDefault();
221
+ localStorage.setItem('hideJunkiesSetupInstructions', 'false');
222
+ setupBox.style.display = 'block';
223
+ showLink.style.display = 'none';
224
+ }});
225
+ </script>
226
+ '''
227
+
228
+ @app.get('/captcha/junkies.user.js')
229
+ def serve_junkies_user_js():
230
+ sj = shared_state.values["config"]("Hostnames").get("sj")
231
+ dj = shared_state.values["config"]("Hostnames").get("dj")
232
+
233
+ content = junkies_user_js(sj, dj)
234
+ response.content_type = 'application/javascript'
235
+ return content
236
+
237
+ @app.get('/captcha/filecrypt.user.js')
238
+ def serve_filecrypt_user_js():
239
+ content = filecrypt_user_js()
107
240
  response.content_type = 'application/javascript'
108
241
  return content
109
242
 
110
- def render_bypass_section(url, package_id, title, password):
243
+ def render_filecrypt_bypass_section(url, package_id, title, password):
111
244
  """Render the bypass UI section for both cutcaptcha and circle captcha pages"""
112
245
 
113
246
  # Generate userscript URL with transfer params
@@ -135,7 +268,7 @@ def setup_captcha_routes(app):
135
268
  <a href="https://www.tampermonkey.net/" target="_blank" rel="noopener noreferrer">1. Install Tampermonkey</a>
136
269
  </p>
137
270
  <p style="margin-top: 0; margin-bottom: 12px;">
138
- <a href="/captcha/quasarr.user.js" target="_blank">2. Install this userscript</a>
271
+ <a href="/captcha/filecrypt.user.js" target="_blank">2. Install this userscript</a>
139
272
  </p>
140
273
  <p style="margin-top: 0;">
141
274
  <button id="hide-setup-btn" type="button" style="background: #444; color: #fff; border: 1px solid #666; padding: 6px 12px; border-radius: 4px; cursor: pointer;">
@@ -146,7 +279,7 @@ def setup_captcha_routes(app):
146
279
 
147
280
  <!-- Hidden "show instructions" link -->
148
281
  <div id="show-instructions-link" style="display: none; margin-bottom: 16px;">
149
- <a href="#" id="show-setup-btn" style="color: #58a6ff; text-decoration: underline;">ℹ️ Show instructions again</a>
282
+ <a href="#" id="show-setup-btn" style="color: #58a6ff;">ℹ️ Show instructions again</a>
150
283
  </div>
151
284
 
152
285
  <strong><a href="{url_with_quick_transfer_params}" target="_blank">🔗 Obtain the download links here!</a></strong><br><br>
@@ -188,7 +321,7 @@ def setup_captcha_routes(app):
188
321
  }}
189
322
 
190
323
  // Handle setup instructions hide/show
191
- const hideSetup = localStorage.getItem('hideSetupInstructions');
324
+ const hideSetup = localStorage.getItem('hideFileCryptSetupInstructions');
192
325
  const setupBox = document.getElementById('setup-instructions');
193
326
  const showLink = document.getElementById('show-instructions-link');
194
327
 
@@ -199,7 +332,7 @@ def setup_captcha_routes(app):
199
332
 
200
333
  // Hide setup instructions
201
334
  document.getElementById('hide-setup-btn').addEventListener('click', function() {{
202
- localStorage.setItem('hideSetupInstructions', 'true');
335
+ localStorage.setItem('hideFileCryptSetupInstructions', 'true');
203
336
  setupBox.style.display = 'none';
204
337
  showLink.style.display = 'block';
205
338
  }});
@@ -207,7 +340,7 @@ def setup_captcha_routes(app):
207
340
  // Show setup instructions again
208
341
  document.getElementById('show-setup-btn').addEventListener('click', function(e) {{
209
342
  e.preventDefault();
210
- localStorage.setItem('hideSetupInstructions', 'false');
343
+ localStorage.setItem('hideFileCryptSetupInstructions', 'false');
211
344
  setupBox.style.display = 'block';
212
345
  showLink.style.display = 'none';
213
346
  }});
@@ -426,7 +559,7 @@ def setup_captcha_routes(app):
426
559
  url = prioritized_links[0][0]
427
560
 
428
561
  # Add bypass section
429
- bypass_section = render_bypass_section(url, package_id, title, password)
562
+ bypass_section = render_filecrypt_bypass_section(url, package_id, title, password)
430
563
 
431
564
  content = render_centered_html(r'''
432
565
  <script type="text/javascript">
@@ -607,6 +740,14 @@ def setup_captcha_routes(app):
607
740
  {render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
608
741
  </p>''')
609
742
 
743
+ package_exists = shared_state.get_db("protected").retrieve(package_id)
744
+ if not package_exists:
745
+ return render_centered_html(f'''<h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
746
+ <p><b>Error:</b> Package not found or already solved.</p>
747
+ <p>
748
+ {render_button("Back", "secondary", {"onclick": "location.href='/captcha'"})}
749
+ </p>''')
750
+
610
751
  # Process links input
611
752
  if links_input:
612
753
  info(f"Processing direct links bypass for {title}")
@@ -787,7 +928,7 @@ def setup_captcha_routes(app):
787
928
  return "Missing required parameters"
788
929
 
789
930
  # Add bypass section
790
- bypass_section = render_bypass_section(original_url, package_id, title, password)
931
+ bypass_section = render_filecrypt_bypass_section(original_url, package_id, title, password)
791
932
 
792
933
  return render_centered_html(f"""
793
934
  <!DOCTYPE html>
@@ -11,6 +11,7 @@ from quasarr.downloads.linkcrypters.hide import decrypt_links_if_hide
11
11
  from quasarr.downloads.sources.al import get_al_download_links
12
12
  from quasarr.downloads.sources.by import get_by_download_links
13
13
  from quasarr.downloads.sources.dd import get_dd_download_links
14
+ from quasarr.downloads.sources.dj import get_dj_download_links
14
15
  from quasarr.downloads.sources.dt import get_dt_download_links
15
16
  from quasarr.downloads.sources.dw import get_dw_download_links
16
17
  from quasarr.downloads.sources.he import get_he_download_links
@@ -18,6 +19,7 @@ from quasarr.downloads.sources.mb import get_mb_download_links
18
19
  from quasarr.downloads.sources.nk import get_nk_download_links
19
20
  from quasarr.downloads.sources.nx import get_nx_download_links
20
21
  from quasarr.downloads.sources.sf import get_sf_download_links, resolve_sf_redirect
22
+ from quasarr.downloads.sources.sj import get_sj_download_links
21
23
  from quasarr.downloads.sources.sl import get_sl_download_links
22
24
  from quasarr.downloads.sources.wd import get_wd_download_links
23
25
  from quasarr.providers.log import info
@@ -201,6 +203,7 @@ def download(shared_state, request_from, title, url, mirror, size_mb, password,
201
203
  'AL': config.get("al"),
202
204
  'BY': config.get("by"),
203
205
  'DD': config.get("dd"),
206
+ 'DJ': config.get("dj"),
204
207
  'DT': config.get("dt"),
205
208
  'DW': config.get("dw"),
206
209
  'HE': config.get("he"),
@@ -208,6 +211,7 @@ def download(shared_state, request_from, title, url, mirror, size_mb, password,
208
211
  'NK': config.get("nk"),
209
212
  'NX': config.get("nx"),
210
213
  'SF': config.get("sf"),
214
+ 'SJ': config.get("sj"),
211
215
  'SL': config.get("sl"),
212
216
  'WD': config.get("wd")
213
217
  }
@@ -216,6 +220,7 @@ def download(shared_state, request_from, title, url, mirror, size_mb, password,
216
220
  (flags['AL'], handle_al),
217
221
  (flags['BY'], handle_by),
218
222
  (flags['DD'], lambda *a: handle_unprotected(*a, func=get_dd_download_links, label='DD')),
223
+ (flags['DJ'], lambda *a: handle_protected(*a, func=get_dj_download_links, label='DJ')),
219
224
  (flags['DT'], lambda *a: handle_unprotected(*a, func=get_dt_download_links, label='DT')),
220
225
  (flags['DW'], lambda *a: handle_protected(*a, func=get_dw_download_links, label='DW')),
221
226
  (flags['HE'], lambda *a: handle_unprotected(*a, func=get_he_download_links, label='HE')),
@@ -223,6 +228,7 @@ def download(shared_state, request_from, title, url, mirror, size_mb, password,
223
228
  (flags['NK'], lambda *a: handle_protected(*a, func=get_nk_download_links, label='NK')),
224
229
  (flags['NX'], lambda *a: handle_unprotected(*a, func=get_nx_download_links, label='NX')),
225
230
  (flags['SF'], handle_sf),
231
+ (flags['SJ'], lambda *a: handle_protected(*a, func=get_sj_download_links, label='SJ')),
226
232
  (flags['SL'], handle_sl),
227
233
  (flags['WD'], handle_wd),
228
234
  ]
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Quasarr
3
+ # Project by https://github.com/rix1337
4
+
5
+
6
+ def get_dj_download_links(shared_state, url, mirror, title): # signature must align with other download link functions!
7
+ return [url]
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Quasarr
3
+ # Project by https://github.com/rix1337
4
+
5
+
6
+ def get_sj_download_links(shared_state, url, mirror, title): # signature must align with other download link functions!
7
+ return [url]
@@ -6,6 +6,7 @@ logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAX
6
6
  al = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA9UlEQVR4nBXBv0rDQADA4d8ld8EQ2yJiGxAchOqgQSFEEEGfQV18DgcHwYcQ36GDg07+wUlwcBApCnUrbYcKIrRIa0qTy534fSI+vbMCi7GSUe7zr6QmOEJjEUhhDcZReHLEQXiLNoKn4S66CHBMjjTKJ0+nHPZPOJrtEPguc+17GuE5fjCD9H57rHYuCT/H3HxUmaYp5aDPRnZBd2kfp9d6I15fYe/qmdryJiVvge3GI1tRnW7rHbF2fG0rXy9E5QFJnoFSNF1Jc1jhp5YgkrMHm2kYtF9R+TdYS6aqzNdjPBek0RolYDHaoSgsWHClQE/GGA1/SzpdtJMWu3cAAAAASUVORK5CYII='
7
7
  by = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAATlBMVEX///8crf/29fQDo//J7f/u7u7w+v/2/P/8/PyytLWenp6IhoWTwNfPz8+mpaVYxP94eHhryf+75fyB0f+oxNPl5eW4yNGrr68xsfmUlJQWKuGqAAAAQklEQVQI1yWLSRKAIBADMzOAsgiC+/8/aiwvSR+6AUB15mJyZ42ev8ldclI4MWvXuvwQCaGblPxQCmNP9fgyqGf+AkFFAeZ3L10cAAAAAElFTkSuQmCC'
8
8
  dd = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABCElEQVR4nAXBP0sCYRzA8e89d6eQ4SRkdNKQOEVSvQQbegdB4dAgLY1tLS1NvYFoDIIIGtqKXPq3JJGWEQkaDQliGJnaPXf6e/p8rGrt4/CxUl777fZQylYGg4iReHyMxez8kXN1c5c/Pj1D7AhKQqKujQgqCDSrK35evTca/InD+sYmyXSWdqeLFvCHhka9bpyvdgsdCpl0msRkin1/xMLcLKXbIs3mp6UG/R5vL2VKzzV29g4oFAokkh7XxQuGgcbxtTYy6Fr97xaZ6QmeHu55rVYIf9poHeAMR8ZCNLvbW8TGY5x0OrgK7KhLEIY4U6nUeS63tBwGWowR5dgziME4bsTyPO/yH3LufRKlNxKMAAAAAElFTkSuQmCC'
9
+ dj = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAC9UExURY2NjYWGhoeIiYiIiIiIiYWFhoODg4mKillZWYSEhFBMS3x7e0xIR3Bsa0E+PldWVi4wMT4/Px0eHiQkJA8PDwoMDAsMDAsMDAsLCw0NDWxqaHZtZnBrZ2dnZ25qZ21oZJ94XtWNWr6DXG9mYpp2X6p5V6JwVNJ9TsN4UXBgWZhtVaxuTZBbR6xiRMpoQKlgQ6JfRZ5cQHFGN6dOMqFOM5lMM6tPMmZBNCYlJS8mIywlJCglJC8mJCMhIf////64+awAAAAadFJOU1SosLCwokSyl7ugup+6n7ugr5RLlJmYmZA8Hg7dEgAAAAFiS0dEPklkAOMAAAAHdElNRQfpDBESHCL8f1a2AAAAUElEQVQI12NgYGRiYmZhZWNgl5KWkZWT52DgVFBUUlZR5WLgVlPX0NTS5mHg1dHV0zcw5GPgNzI2MTUzF2AQtLC0sraxFWIQFhEVExOXkAQA4WIHfGNBCckAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjUtMTItMTdUMTg6Mjg6MjgrMDA6MDDR5B2HAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDI1LTEyLTE3VDE4OjI4OjI4KzAwOjAwoLmlOwAAACh0RVh0ZGF0ZTp0aW1lc3RhbXAAMjAyNS0xMi0xN1QxODoyODozNCswMDowMPym7g4AAAAASUVORK5CYII='
9
10
  dt = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA4ElEQVR4nD2PsWrCUBSGv3ubBoq+QevQUhAXV6dMRcyTZBV8CCfxCTq4ZOmSJwg+giB4t16p1NCpSFECyY2n5EL7LeeD8/PDr5qmEa01+XrNz35PaAxFUXCrNQ/DIVophbWW19WKt+WSp16PYLvlvtNBHY8ESZLg6ppPaxmMRgymUz4WC577fR5nM3SapryMx0RRxPf5jDjHXRi21bQEWZZhjCGeTIiDgOvpxM3hQLXb+QAiImVZSlVV0tS1XEXkPY7laz5vX0K74h/n/KnyXNxm41351B+tKoVcLt5Vt8svgsSBPKnRQSAAAAAASUVORK5CYII='
10
11
  dw = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA70lEQVR4nAXBTUrDQBiA4febmfjbpqZUsFkJxS6UIq4EvYS46CUExRPkBL2HFM/gIdxIFq4Ff2JNia3JzBefR+bzuZ1Op+E+y/q6Fd/GSe8qijZH1lpfN00uADdZNt5NDh7GJ8dng2HKxvYOBtB6jctms/5S3ePpxeWklw69tZE1IqKqrIxT81U1d4PReCJJ338HXBVURCCokhelMZW467YTt9pi1rUntPCHYJ0ljiyubMJh0yJFuZSk26ENjufPFV0CCeAUfLEo8V7pRBHWOMJvBc5Q+xpnIf8pl+fvi1Lztw9zlO6T7sWw9jy9vPIPUL1kPErau3YAAAAASUVORK5CYII='
11
12
  fx = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABB0lEQVR4nB2LP0sCcQBA3/2Uzugu8E4IoT9cSYhUENFWYSA1FFTQ4hK5SENNDW19hoYcImhvaGgLguYgcIk0haLOE7wrNGzo5A4v9K3vPWknmwt0LYrrunieTygkAAm300EIQTg5nWB3e5O39w9s54t4fISEYfBZq/H4VETIsoxp1ftyJpWkR7lSZXFhnsFIhLBtOxR9n1KliqZFeX4po+saZ4VLTMtC9M61TJrTk2OarR9WV5bIpJfZ2ljH8zxEw3EovVaZm01xd//AsKpwfXNLLKaTmDSQ9vJHwWE+x3ezRbv9i6oqTBkTmLU65xdXSNn9g0CWB2jYDuNjo/y5Lqoy1A+63S7/GeVj+5KBt3UAAAAASUVORK5CYII='
@@ -14,5 +15,6 @@ he = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNS
14
15
  mb = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABE0lEQVR4nAEIAff+AQAAAACHttAxAQ0Kjd7o7ToDAQH/GREOxcDT4HK+cErSAYy70DHs/AG6tNjoFNzn6gD5+fwAQjElAIE/JuqWxdZEBBwVD5RSIhsUnsTV/JBRNwT9/v4AjcDU/DsgOxbW6RqUArra5zf39vb/KRwTBKPN3gBMKhwA3unyBJHE2P/i7/U6Auvz+ACkzt4AaTkmAN/u9AARCQYAQysdALXW5ADl8fYAAi4bEMbD5vQBMxoR/CwYEACVxtkAfUMr/PoECAEZDwrFAiAPCmwpEAjq/gcKBAUDAQCQwtcAMSIZBDkbDukIBAJtAmU5KNIP//1F1ePrvScWD/hSLR/4uNXkvAHz9kZ+RS/T0LpzUht5tWAAAAAASUVORK5CYII='
15
16
  nx = 'data:image/png;base64,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'
16
17
  sf = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA8ElEQVR4nBXBv0rDQADA4d/lz1lqmlREECUIFTq4FBeHDA6uPkRfwwfxBRx19gmk4GZBBAcjCAUrWEkaTUzucjnx+0R6kzQboZDCb7AWdNmiMo3KLHUmlCcDR/Z2DujvJXRth6kNqqgoXufo6kk6lpzwcEr7fUrx7POTugh9xPZkilE5jh9MsJ2mreaUi0eKdIa7CUIogjjB8wcnyOiM0rmkH+8ih8dY1gTxOeHoA69eLRCOxBvsE21JOmVo8gy3N6T+fMNbPdwiwzG6XFJ/vWC1xY9GvN9dsZxdI+4vxo3rtxLH8q/TFvPbYhqL0Z76AydWbEnTqqOqAAAAAElFTkSuQmCC'
18
+ sj = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAC9UExURY2NjYWGhoeIiYiIiIiIiYWFhoODg4mKillZWYSEhFBMS3x7e0xIR3Bsa0E+PldWVi4wMT4/Px0eHiQkJA8PDwoMDAsMDAsMDAsLCw0NDWxqaHZtZnBrZ2dnZ25qZ21oZJ94XtWNWr6DXG9mYpp2X6p5V6JwVNJ9TsN4UXBgWZhtVaxuTZBbR6xiRMpoQKlgQ6JfRZ5cQHFGN6dOMqFOM5lMM6tPMmZBNCYlJS8mIywlJCglJC8mJCMhIf////64+awAAAAadFJOU1SosLCwokSyl7ugup+6n7ugr5RLlJmYmZA8Hg7dEgAAAAFiS0dEPklkAOMAAAAHdElNRQfpDBESHCL8f1a2AAAAUElEQVQI12NgYGRiYmZhZWNgl5KWkZWT52DgVFBUUlZR5WLgVlPX0NTS5mHg1dHV0zcw5GPgNzI2MTUzF2AQtLC0sraxFWIQFhEVExOXkAQA4WIHfGNBCckAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjUtMTItMTdUMTg6Mjg6MjgrMDA6MDDR5B2HAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDI1LTEyLTE3VDE4OjI4OjI4KzAwOjAwoLmlOwAAACh0RVh0ZGF0ZTp0aW1lc3RhbXAAMjAyNS0xMi0xN1QxODoyODozNCswMDowMPym7g4AAAAASUVORK5CYII='
17
19
  sl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA/ElEQVR4nE3PrUpDYRzH8d/z5tnRozugImIYgjAwGY5egGUG64JRkxgWvIFV0zCIwVsQLLYhWgSDLizJiowVFUHZdJ7zvP7Ftu8NfPgybOwvImcFet0C6FhMtrYTSQg1Ru/id3W7sfTxmc3rQpeE4gk8BZJiigF1kWYLe9rSGZEvR4o/AvRAxBLB8SObzXVqXb/tBjFdJjOCtliJI/U6fDo/CQSwf4ozIM2OGrm2xyGEClQCReO76tZmTabZYcsHmnHOfSvJ+8axCiNtlJDtztdNkLmxbViyEIx7669YxPPlZG44uD99wXs9ltqNbvF8aSbvBgBH9WAWMZX+AJIYacQLryqFAAAAAElFTkSuQmCC'
18
20
  wd = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA90lEQVR4nC3OsS5DUQAG4P8/Pb2kiS4mliaEkMYgTBiQiMVk6C6RsHmAxuoFbHTzCmIR9QSWLko6kIiQCnqHq+fec84vEt8TfMS/5s3jniFf36uD9sTH6Fg9rQ8aDQY2r7trhJmFUY1kW9I4gF0I9wGuZUOM04x+B4o9EvMSZ0D1EVWYkklsnn51TFIOhJ2DwgaN6QiqgsbRh00rseu9J1WsUngGOBmhSvaZnZwdbA1MkWfrwQ2PvXMvwbk7n7taLPKppFpe+cvb8DN8gi09RGmkbEv9GHQLqQZi+bB19WZDdN+WlYskRe/0aNsBaO+fXy4ZxEWKC7/R+XkmFbstdAAAAABJRU5ErkJggg=='
@@ -159,7 +159,7 @@ def render_centered_html(inner_content):
159
159
  text-decoration: none;
160
160
  }
161
161
  a:hover {
162
- text-decoration: underline;
162
+
163
163
  }
164
164
  /* footer styling */
165
165
  footer {