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.

Files changed (77) hide show
  1. quasarr/__init__.py +316 -42
  2. quasarr/api/__init__.py +187 -0
  3. quasarr/api/arr/__init__.py +387 -0
  4. quasarr/api/captcha/__init__.py +1189 -0
  5. quasarr/api/config/__init__.py +23 -0
  6. quasarr/api/sponsors_helper/__init__.py +166 -0
  7. quasarr/api/statistics/__init__.py +196 -0
  8. quasarr/downloads/__init__.py +319 -256
  9. quasarr/downloads/linkcrypters/__init__.py +0 -0
  10. quasarr/downloads/linkcrypters/al.py +237 -0
  11. quasarr/downloads/linkcrypters/filecrypt.py +444 -0
  12. quasarr/downloads/linkcrypters/hide.py +123 -0
  13. quasarr/downloads/packages/__init__.py +476 -0
  14. quasarr/downloads/sources/al.py +697 -0
  15. quasarr/downloads/sources/by.py +106 -0
  16. quasarr/downloads/sources/dd.py +76 -0
  17. quasarr/downloads/sources/dj.py +7 -0
  18. quasarr/downloads/sources/dl.py +199 -0
  19. quasarr/downloads/sources/dt.py +66 -0
  20. quasarr/downloads/sources/dw.py +14 -7
  21. quasarr/downloads/sources/he.py +112 -0
  22. quasarr/downloads/sources/mb.py +47 -0
  23. quasarr/downloads/sources/nk.py +54 -0
  24. quasarr/downloads/sources/nx.py +42 -83
  25. quasarr/downloads/sources/sf.py +159 -0
  26. quasarr/downloads/sources/sj.py +7 -0
  27. quasarr/downloads/sources/sl.py +90 -0
  28. quasarr/downloads/sources/wd.py +110 -0
  29. quasarr/downloads/sources/wx.py +127 -0
  30. quasarr/providers/cloudflare.py +204 -0
  31. quasarr/providers/html_images.py +22 -0
  32. quasarr/providers/html_templates.py +211 -104
  33. quasarr/providers/imdb_metadata.py +108 -3
  34. quasarr/providers/log.py +19 -0
  35. quasarr/providers/myjd_api.py +201 -40
  36. quasarr/providers/notifications.py +99 -11
  37. quasarr/providers/obfuscated.py +65 -0
  38. quasarr/providers/sessions/__init__.py +0 -0
  39. quasarr/providers/sessions/al.py +286 -0
  40. quasarr/providers/sessions/dd.py +78 -0
  41. quasarr/providers/sessions/dl.py +175 -0
  42. quasarr/providers/sessions/nx.py +76 -0
  43. quasarr/providers/shared_state.py +656 -79
  44. quasarr/providers/statistics.py +154 -0
  45. quasarr/providers/version.py +60 -1
  46. quasarr/providers/web_server.py +1 -1
  47. quasarr/search/__init__.py +144 -15
  48. quasarr/search/sources/al.py +448 -0
  49. quasarr/search/sources/by.py +204 -0
  50. quasarr/search/sources/dd.py +135 -0
  51. quasarr/search/sources/dj.py +213 -0
  52. quasarr/search/sources/dl.py +354 -0
  53. quasarr/search/sources/dt.py +265 -0
  54. quasarr/search/sources/dw.py +94 -67
  55. quasarr/search/sources/fx.py +89 -33
  56. quasarr/search/sources/he.py +196 -0
  57. quasarr/search/sources/mb.py +195 -0
  58. quasarr/search/sources/nk.py +188 -0
  59. quasarr/search/sources/nx.py +75 -21
  60. quasarr/search/sources/sf.py +374 -0
  61. quasarr/search/sources/sj.py +213 -0
  62. quasarr/search/sources/sl.py +246 -0
  63. quasarr/search/sources/wd.py +208 -0
  64. quasarr/search/sources/wx.py +337 -0
  65. quasarr/storage/config.py +39 -10
  66. quasarr/storage/setup.py +269 -97
  67. quasarr/storage/sqlite_database.py +6 -1
  68. quasarr-1.23.0.dist-info/METADATA +306 -0
  69. quasarr-1.23.0.dist-info/RECORD +77 -0
  70. {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info}/WHEEL +1 -1
  71. quasarr/arr/__init__.py +0 -423
  72. quasarr/captcha_solver/__init__.py +0 -284
  73. quasarr-0.1.6.dist-info/METADATA +0 -81
  74. quasarr-0.1.6.dist-info/RECORD +0 -31
  75. {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info}/entry_points.txt +0 -0
  76. {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info/licenses}/LICENSE +0 -0
  77. {quasarr-0.1.6.dist-info → quasarr-1.23.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Quasarr
3
+ # Project by https://github.com/rix1337
4
+
5
+ from quasarr.providers.html_templates import render_form
6
+ from quasarr.providers.html_templates import render_button
7
+ from quasarr.storage.setup import hostname_form_html, save_hostnames
8
+
9
+
10
+ def setup_config(app, shared_state):
11
+ @app.get('/hostnames')
12
+ def hostnames_ui():
13
+ message = """<p>
14
+ At least one hostname must be kept.
15
+ </p>"""
16
+ back_button = f'''<p>
17
+ {render_button("Back", "secondary", {"onclick": "location.href='/'"})}
18
+ </p>'''
19
+ return render_form("Hostnames", hostname_form_html(shared_state, message) + back_button)
20
+
21
+ @app.post("/api/hostnames")
22
+ def hostnames_api():
23
+ return save_hostnames(shared_state, timeout=1, first_run=False)
@@ -0,0 +1,166 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Quasarr
3
+ # Project by https://github.com/rix1337
4
+
5
+ import json
6
+
7
+ from bottle import request, abort
8
+
9
+ from quasarr.downloads import fail
10
+ from quasarr.providers import shared_state
11
+ from quasarr.providers.log import info
12
+ from quasarr.providers.notifications import send_discord_message
13
+ from quasarr.providers.statistics import StatsHelper
14
+
15
+
16
+ def setup_sponsors_helper_routes(app):
17
+ @app.get("/sponsors_helper/api/to_decrypt/")
18
+ def to_decrypt_api():
19
+ try:
20
+ if not shared_state.values["helper_active"]:
21
+ shared_state.update("helper_active", True)
22
+ info(f"Sponsor status activated successfully")
23
+
24
+ protected = shared_state.get_db("protected").retrieve_all_titles()
25
+ if not protected:
26
+ return abort(404, "No encrypted packages found")
27
+
28
+ # Find the first package without a "session" key
29
+ selected_package = None
30
+ for package in protected:
31
+ data = json.loads(package[1])
32
+ if "session" not in data:
33
+ selected_package = (package[0], data)
34
+ break
35
+
36
+ if not selected_package:
37
+ return abort(404, "No valid packages without session found")
38
+
39
+ package_id, data = selected_package
40
+ title = data["title"]
41
+ links = data["links"]
42
+ mirror = None if (mirror := data.get('mirror')) == "None" else mirror
43
+ password = data["password"]
44
+
45
+ rapid = [ln for ln in links if "rapidgator" in ln[1].lower()]
46
+ others = [ln for ln in links if "rapidgator" not in ln[1].lower()]
47
+ prioritized_links = rapid + others
48
+
49
+ return {
50
+ "to_decrypt": {
51
+ "name": title,
52
+ "id": package_id,
53
+ "url": prioritized_links,
54
+ "mirror": mirror,
55
+ "password": password,
56
+ "max_attempts": 3
57
+ }
58
+ }
59
+ except Exception as e:
60
+ return abort(500, str(e))
61
+
62
+ @app.post("/sponsors_helper/api/to_download/")
63
+ def to_download_api():
64
+ try:
65
+ data = request.json
66
+ title = data.get('name')
67
+ package_id = data.get('package_id')
68
+ download_links = data.get('urls')
69
+ password = data.get('password')
70
+
71
+ info(f"Received {len(download_links)} download links for {title}")
72
+
73
+ if download_links:
74
+ downloaded = shared_state.download_package(download_links, title, password, package_id)
75
+ if downloaded:
76
+ StatsHelper(shared_state).increment_package_with_links(download_links)
77
+ StatsHelper(shared_state).increment_captcha_decryptions_automatic()
78
+ shared_state.get_db("protected").delete(package_id)
79
+ send_discord_message(shared_state, title=title, case="solved")
80
+ info(f"Download successfully started for {title}")
81
+ return f"Downloaded {len(download_links)} download links for {title}"
82
+ else:
83
+ info(f"Download failed for {title}")
84
+
85
+ except Exception as e:
86
+ info(f"Error decrypting: {e}")
87
+
88
+ StatsHelper(shared_state).increment_failed_decryptions_automatic()
89
+ return abort(500, "Failed") #
90
+
91
+ @app.post("/sponsors_helper/api/to_replace/")
92
+ def to_replace_api():
93
+ try:
94
+ data = request.json
95
+ name = data.get('name')
96
+ package_id = data.get('package_id')
97
+ password = data.get('password')
98
+ replace_url = data.get('replace_url')
99
+ mirror = data.get('mirror')
100
+ session = data.get('session')
101
+
102
+ if not all([name, package_id, replace_url, mirror, session]):
103
+ info("Missing required replacement data")
104
+ return {"error": "Missing required replacement data"}, 400
105
+
106
+ if password is None:
107
+ password = ""
108
+
109
+ blob = json.dumps(
110
+ {
111
+ "title": name,
112
+ "links": [replace_url, mirror],
113
+ "size_mb": 0,
114
+ "password": password,
115
+ "mirror": mirror,
116
+ "session": session
117
+ })
118
+
119
+ shared_state.get_db("protected").update_store(package_id, blob)
120
+
121
+ info(f"Another CAPTCHA solution is required for {mirror} link: {replace_url}")
122
+
123
+ StatsHelper(shared_state).increment_captcha_decryptions_automatic()
124
+
125
+ return f"Replacement link stored for {name}"
126
+
127
+ except Exception as e:
128
+ StatsHelper(shared_state).increment_failed_decryptions_automatic()
129
+ info(f"Error handling replacement: {e}")
130
+ return {"error": str(e)}, 500
131
+
132
+ @app.delete("/sponsors_helper/api/to_failed/")
133
+ def move_to_failed_api():
134
+ try:
135
+ StatsHelper(shared_state).increment_failed_decryptions_automatic()
136
+
137
+ data = request.json
138
+ package_id = data.get('package_id')
139
+
140
+ data = json.loads(shared_state.get_db("protected").retrieve(package_id))
141
+ title = data.get('title')
142
+
143
+ if package_id:
144
+ info(f'Marking package "{title}" with ID "{package_id}" as failed')
145
+ failed = fail(title, package_id, shared_state, reason="Too many failed attempts by SponsorsHelper")
146
+ if failed:
147
+ shared_state.get_db("protected").delete(package_id)
148
+ send_discord_message(shared_state, title=title, case="failed")
149
+ return f'Package "{title}" with ID "{package_id} marked as failed!"'
150
+ except Exception as e:
151
+ info(f"Error moving to failed: {e}")
152
+
153
+ return abort(500, "Failed")
154
+
155
+ @app.put("/sponsors_helper/api/set_sponsor_status/")
156
+ def activate_sponsor_status():
157
+ try:
158
+ data = request.body.read().decode("utf-8")
159
+ payload = json.loads(data)
160
+ if payload["activate"]:
161
+ shared_state.update("helper_active", True)
162
+ info(f"Sponsor status activated successfully")
163
+ return "Sponsor status activated successfully!"
164
+ except:
165
+ pass
166
+ return abort(500, "Failed")
@@ -0,0 +1,196 @@
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.providers.html_templates import render_button, render_centered_html
7
+ from quasarr.providers.statistics import StatsHelper
8
+
9
+
10
+ def setup_statistics(app, shared_state):
11
+ @app.get('/statistics')
12
+ def statistics():
13
+ stats_helper = StatsHelper(shared_state)
14
+ stats = stats_helper.get_stats()
15
+
16
+ stats_html = f"""
17
+ <h1><img src="{images.logo}" type="image/png" alt="Quasarr logo" class="logo"/>Quasarr</h1>
18
+ <h2>Statistics</h2>
19
+ <div class="stats-container">
20
+ <h3>📊 Overview</h3>
21
+ <div class="stats-grid compact">
22
+ <div class="stat-card highlight">
23
+ <h3>📦 Total Download Attempts</h3>
24
+ <div class="stat-value">{stats['total_download_attempts']}</div>
25
+ <div class="stat-subtitle">Success Rate: {stats['download_success_rate']:.1f}%</div>
26
+ </div>
27
+ <div class="stat-card highlight">
28
+ <h3>🔐 Total CAPTCHA Decryptions</h3>
29
+ <div class="stat-value">{stats['total_captcha_decryptions']}</div>
30
+ <div class="stat-subtitle">Success Rate: {stats['decryption_success_rate']:.1f}%</div>
31
+ </div>
32
+ </div>
33
+
34
+ <h3>📥 Downloads</h3>
35
+ <div class="stats-grid compact">
36
+ <div class="stat-card">
37
+ <h3>✅ Packages Downloaded</h3>
38
+ <div class="stat-value">{stats['packages_downloaded']}</div>
39
+ </div>
40
+ <div class="stat-card">
41
+ <h3>⚙️ Links Processed</h3>
42
+ <div class="stat-value">{stats['links_processed']}</div>
43
+ </div>
44
+ <div class="stat-card">
45
+ <h3>❌ Failed Downloads</h3>
46
+ <div class="stat-value">{stats['failed_downloads']}</div>
47
+ </div>
48
+ <div class="stat-card">
49
+ <h3>🔗 Average Links per Package</h3>
50
+ <div class="stat-value">{stats['average_links_per_package']:.1f}</div>
51
+ </div>
52
+ </div>
53
+
54
+ <h3>🧩 CAPTCHAs</h3>
55
+ <div class="stats-grid compact">
56
+ <div class="stat-card">
57
+ <h3>🤖 Automatic Decryptions</h3>
58
+ <div class="stat-value">{stats['captcha_decryptions_automatic']}</div>
59
+ <div class="stat-subtitle">Success Rate: {stats['automatic_decryption_success_rate']:.1f}%</div>
60
+ </div>
61
+ <div class="stat-card">
62
+ <h3>👤 Manual Decryptions</h3>
63
+ <div class="stat-value">{stats['captcha_decryptions_manual']}</div>
64
+ <div class="stat-subtitle">Success Rate: {stats['manual_decryption_success_rate']:.1f}%</div>
65
+ </div>
66
+ <div class="stat-card">
67
+ <h3>⛔ Failed Auto Decryptions</h3>
68
+ <div class="stat-value">{stats['failed_decryptions_automatic']}</div>
69
+ </div>
70
+ <div class="stat-card">
71
+ <h3>🚫 Failed Manual Decryptions</h3>
72
+ <div class="stat-value">{stats['failed_decryptions_manual']}</div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <p>
78
+ {render_button("Back", "secondary", {"onclick": "location.href='/'"})}
79
+ </p>
80
+
81
+ <style>
82
+ .stats-container {{
83
+ max-width: 1000px;
84
+ margin: 0 auto;
85
+ }}
86
+
87
+ .stats-grid {{
88
+ display: grid;
89
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
90
+ gap: 15px;
91
+ margin: 15px 0;
92
+ }}
93
+
94
+ .stats-grid.compact {{
95
+ gap: 12px;
96
+ margin: 12px 0;
97
+ }}
98
+
99
+ .stat-card {{
100
+ background: var(--card-bg, #f8f9fa);
101
+ border: 1px solid var(--card-border, #dee2e6);
102
+ border-radius: 8px;
103
+ padding: 15px;
104
+ text-align: center;
105
+ transition: transform 0.2s, box-shadow 0.2s;
106
+ }}
107
+
108
+ .stat-card:hover {{
109
+ transform: translateY(-2px);
110
+ box-shadow: 0 4px 12px var(--card-shadow, rgba(0,0,0,0.1));
111
+ }}
112
+
113
+ .stat-card.highlight {{
114
+ background: var(--highlight-bg, #e3f2fd);
115
+ border-color: var(--highlight-border, #2196f3);
116
+ }}
117
+
118
+ .stat-card h3 {{
119
+ margin: 0 0 8px 0;
120
+ font-size: 13px;
121
+ color: var(--text-muted, #666);
122
+ text-transform: uppercase;
123
+ letter-spacing: 0.5px;
124
+ }}
125
+
126
+ .stat-value {{
127
+ font-size: 24px;
128
+ font-weight: bold;
129
+ color: var(--text-primary, #333);
130
+ margin: 8px 0;
131
+ }}
132
+
133
+ .stat-subtitle {{
134
+ font-size: 11px;
135
+ color: var(--text-secondary, #888);
136
+ margin-top: 4px;
137
+ }}
138
+
139
+ h3 {{
140
+ color: var(--heading-color, #444);
141
+ padding-bottom: 8px;
142
+ margin-top: 25px;
143
+ margin-bottom: 15px;
144
+ }}
145
+
146
+ /* Dark mode styles */
147
+ @media (prefers-color-scheme: dark) {{
148
+ :root {{
149
+ --card-border: #4a5568;
150
+ --card-shadow: rgba(0,0,0,0.3);
151
+ --highlight-bg: #1a365d;
152
+ --highlight-border: #3182ce;
153
+ --text-muted: #a0aec0;
154
+ --text-primary: #f7fafc;
155
+ --text-secondary: #cbd5e0;
156
+ --heading-color: #e2e8f0;
157
+ --border-color: #4a5568;
158
+ }}
159
+ }}
160
+
161
+ /* Force dark mode styles for applications that don't support prefers-color-scheme */
162
+ body.dark-mode .stat-card {{
163
+ background: #2d3748;
164
+ border-color: #4a5568;
165
+ color: #f7fafc;
166
+ }}
167
+
168
+ body.dark-mode .stat-card:hover {{
169
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
170
+ }}
171
+
172
+ body.dark-mode .stat-card.highlight {{
173
+ background: #1a365d;
174
+ border-color: #3182ce;
175
+ }}
176
+
177
+ body.dark-mode .stat-card h3 {{
178
+ color: #a0aec0;
179
+ }}
180
+
181
+ body.dark-mode .stat-value {{
182
+ color: #f7fafc;
183
+ }}
184
+
185
+ body.dark-mode .stat-subtitle {{
186
+ color: #cbd5e0;
187
+ }}
188
+
189
+ body.dark-mode h2 {{
190
+ color: #e2e8f0;
191
+ border-bottom-color: #4a5568;
192
+ }}
193
+ </style>
194
+ """
195
+
196
+ return render_centered_html(stats_html)