zerocheck 1.0.0__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: zerocheck
3
+ Version: 1.0.0
4
+ Summary: Blazing fast Spotify Cookie Checker with hardware-locked licensing
5
+ Author-email: Zyco <zyco@zyco1.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: textual>=0.40.0
9
+ Requires-Dist: requests>=2.28.0
10
+ Requires-Dist: beautifulsoup4>=4.11.0
11
+
12
+ # ZeroChecker 🎧
13
+
14
+ Blazing fast Spotify Cookie Checker with a sleek terminal UI and hardware-locked licensing.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install zerocheck
@@ -0,0 +1,8 @@
1
+ # ZeroChecker 🎧
2
+
3
+ Blazing fast Spotify Cookie Checker with a sleek terminal UI and hardware-locked licensing.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install zerocheck
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zerocheck"
7
+ version = "1.0.0"
8
+ description = "Blazing fast Spotify Cookie Checker with hardware-locked licensing"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ authors = [
12
+ {name = "Zyco", email = "zyco@zyco1.com"}
13
+ ]
14
+ dependencies = [
15
+ "textual>=0.40.0",
16
+ "requests>=2.28.0",
17
+ "beautifulsoup4>=4.11.0",
18
+ ]
19
+
20
+ [project.scripts]
21
+ zerocheck = "zerocheck.app:main"
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["."]
25
+ include = ["zerocheck*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ """ZeroChecker - Spotify Cookie Checker TUI"""
2
+ __version__ = "1.0.0"
@@ -0,0 +1,5 @@
1
+ """Allows running via `python -m zerocheck`"""
2
+ from .app import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,515 @@
1
+ """Main ZeroChecker TUI application"""
2
+
3
+ import os
4
+ import sys
5
+ import time
6
+ import threading
7
+ from datetime import datetime
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+
10
+ try:
11
+ import tkinter as tk
12
+ from tkinter import filedialog
13
+ GUI_AVAILABLE = True
14
+ except ImportError:
15
+ GUI_AVAILABLE = False
16
+
17
+ from textual.app import App, ComposeResult
18
+ from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
19
+ from textual.widgets import Header, Footer, Static, Label, Input, Button, RichLog, Checkbox
20
+ from textual.screen import ModalScreen
21
+ from textual.reactive import reactive
22
+
23
+ from .backend import process_one_cookie, parse_proxy_file
24
+ from .auth import validate_license_key
25
+
26
+
27
+ def resize_terminal(rows: int = 34, cols: int = 110):
28
+ """Attempt to resize the terminal window."""
29
+ try:
30
+ sys.stdout.write(f"\033[8;{rows};{cols}t")
31
+ sys.stdout.flush()
32
+ except Exception:
33
+ pass
34
+
35
+
36
+ def gui_select_folder(title="Select a folder"):
37
+ """Open folder selection dialog."""
38
+ if not GUI_AVAILABLE: return None
39
+ try:
40
+ root = tk.Tk(); root.withdraw(); root.attributes("-topmost", True); root.update()
41
+ folder = filedialog.askdirectory(parent=root, title=title); root.destroy(); return folder
42
+ except Exception:
43
+ try: root.destroy()
44
+ except Exception: pass
45
+ return None
46
+
47
+
48
+ def gui_select_file(title="Select a file"):
49
+ """Open file selection dialog."""
50
+ if not GUI_AVAILABLE: return None
51
+ try:
52
+ root = tk.Tk(); root.withdraw(); root.attributes("-topmost", True); root.update()
53
+ filepath = filedialog.askopenfilename(parent=root, title=title); root.destroy(); return filepath
54
+ except Exception:
55
+ try: root.destroy()
56
+ except Exception: pass
57
+ return None
58
+
59
+
60
+ class LicenseScreen(ModalScreen):
61
+ """Modal license key entry screen."""
62
+
63
+ DEFAULT_CSS = """
64
+ LicenseScreen {
65
+ align: center middle;
66
+ }
67
+
68
+ #license_container {
69
+ width: 70;
70
+ height: 15;
71
+ background: #0a150e;
72
+ border: thick #1DB954;
73
+ padding: 1;
74
+ }
75
+
76
+ #license_container Label {
77
+ color: #1DB954;
78
+ text-style: bold;
79
+ margin-bottom: 1;
80
+ }
81
+
82
+ #license_container Input {
83
+ margin-bottom: 1;
84
+ border: solid #1DB954;
85
+ }
86
+
87
+ #license_container Button {
88
+ width: 100%;
89
+ margin-top: 1;
90
+ background: #1DB954;
91
+ color: #000000;
92
+ text-style: bold;
93
+ }
94
+
95
+ #license_container Button:hover {
96
+ background: #1ED760;
97
+ }
98
+
99
+ #license_message {
100
+ color: #ff4444;
101
+ text-style: bold;
102
+ margin-top: 1;
103
+ }
104
+ """
105
+
106
+ def compose(self):
107
+ with Vertical(id="license_container"):
108
+ yield Label("🔐 LICENSE KEY REQUIRED")
109
+ yield Label("Enter your license key to activate:")
110
+ yield Input(placeholder="ZC-XXXX-XXXX-XXXX", id="license_input")
111
+ yield Button("ACTIVATE", id="activate_button")
112
+ yield Label("", id="license_message")
113
+
114
+ def on_button_pressed(self, event: Button.Pressed) -> None:
115
+ if event.button.id == "activate_button":
116
+ license_key = self.query_one("#license_input", Input).value
117
+ message_label = self.query_one("#license_message", Label)
118
+
119
+ if not license_key:
120
+ message_label.update("Please enter a license key")
121
+ return
122
+
123
+ message_label.update("Validating...")
124
+
125
+ # Validate with Keygen
126
+ success, message = validate_license_key(license_key)
127
+
128
+ if success:
129
+ message_label.styles.color = "#1ED760"
130
+ message_label.update(f"✅ {message}")
131
+ self.dismiss(True)
132
+ else:
133
+ message_label.styles.color = "#ff4444"
134
+ message_label.update(f"❌ {message}")
135
+
136
+
137
+ class ZeroHeader(Static):
138
+ def render(self) -> str:
139
+ return (
140
+ "[bold #1DB954]"
141
+ " ███████╗███████╗██████╗ ██████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗███████╗██████╗\n"
142
+ " ╚══███╔╝██╔════╝██╔══██╗██╔═══██╗██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝██╔════╝██╔══██╗\n"
143
+ " ███╔╝ █████╗ ██████╔╝██║ ██║██║ ███████║█████╗ ██║ █████╔╝ █████╗ ██████╔╝\n"
144
+ " ███╔╝ ██╔══╝ ██╔══██╗██║ ██║██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ██╔══╝ ██╔══██╗\n"
145
+ " ███████╗███████╗██║ ██║╚██████╔╝╚██████╗██║ ██║███████╗╚██████╗██║ ██╗███████╗██║ ██║\n"
146
+ " ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝\n"
147
+ " ════════════════════════════════════════════════════════════════════════════════════════[/]"
148
+ )
149
+
150
+
151
+ class Dashboard(Static):
152
+ total = reactive(0)
153
+ success = reactive(0)
154
+ failed = reactive(0)
155
+ rate = reactive(0.0)
156
+ output_dir = reactive("")
157
+ proxies = reactive(0)
158
+ elapsed = reactive("00:00:00")
159
+
160
+ def render(self) -> str:
161
+ return (
162
+ f"📁 TOTAL: {self.total} "
163
+ f"✅ WORKING: {self.success} "
164
+ f"❌ INVALID: {self.failed} "
165
+ f"🚀 RATE: {self.rate:.1f}/s "
166
+ f"🌐 PROXIES: {self.proxies} "
167
+ f"⏱ {self.elapsed}"
168
+ )
169
+
170
+
171
+ class LogPanel(RichLog):
172
+ pass
173
+
174
+
175
+ class ZeroCheckerApp(App):
176
+ CSS = """
177
+ Screen { background: #000000; color: #eeeeee; }
178
+
179
+ #zeroheader { height: 7; min-height: 7; width: 100%; background: #000000; color: #1DB954; content-align: center middle; padding: 0; }
180
+
181
+ #dashboard { height: auto; min-height: 3; width: 100%; margin: 0 1; padding: 0 1; border: round #1DB954; background: #0a150e; color: #ffffff; content-align: left middle; }
182
+
183
+ #main_area { height: 1fr; width: 100%; }
184
+
185
+ #left_column { width: 45%; min-width: 42; height: 1fr; layout: vertical; }
186
+
187
+ #setup_container { height: 1fr; width: 100%; margin: 0 1; border: round #1DB954; background: #0a150e; overflow-y: auto; }
188
+
189
+ #setup_panel { width: 100%; height: auto; padding: 0 1; background: #0a150e; }
190
+
191
+ #setup_panel > Label:first-child { height: 2; color: #1DB954; text-style: bold; content-align: left middle; }
192
+
193
+ #setup_panel Horizontal { width: 100%; height: 3; margin: 0; align: left middle; }
194
+
195
+ #setup_panel Horizontal > Label { width: 16; min-width: 16; color: #1DB954; text-style: bold; content-align: left middle; }
196
+
197
+ #setup_panel Button { width: 12; min-width: 12; height: 3; margin: 0 1 0 0; background: #1DB954; color: #000000; text-style: bold; }
198
+ #setup_panel Button:hover { background: #1ED760; color: #000000; }
199
+
200
+ #setup_panel Input { width: 12; height: 3; margin: 0 1 0 0; border: solid #1DB954; }
201
+ #setup_panel Input:focus { border: tall #1ED760; }
202
+
203
+ #setup_panel Checkbox { width: 16; min-width: 16; height: 3; margin: 0 1 0 0; }
204
+
205
+ #start_button_container { height: 4; min-height: 4; width: 100%; margin: 0 1; align: center middle; }
206
+
207
+ #start_button_container Horizontal {
208
+ width: 100%;
209
+ height: 3;
210
+ align: center middle;
211
+ }
212
+
213
+ #btn_start {
214
+ width: 1fr;
215
+ height: 3;
216
+ margin-right: 1;
217
+ background: #1DB954;
218
+ color: #000000;
219
+ text-style: bold;
220
+ content-align: center middle;
221
+ }
222
+ #btn_start:hover { background: #1ED760; }
223
+ #btn_start:disabled { background: #145A2A; color: #8FD9A6; }
224
+
225
+ #btn_stop {
226
+ width: 1fr;
227
+ height: 3;
228
+ margin-left: 1;
229
+ background: #ff4444;
230
+ color: #ffffff;
231
+ text-style: bold;
232
+ content-align: center middle;
233
+ }
234
+ #btn_stop:hover { background: #ff6666; }
235
+ #btn_stop:disabled { background: #5a1414; color: #a68f8f; }
236
+
237
+ #log { height: 1fr; width: 55%; min-width: 42; margin: 0 1 0 0; padding: 0 1; border: round #1DB954; background: #050d08; scrollbar-size: 1 1; scrollbar-color: #1DB954 #0a150e; }
238
+
239
+ Footer { height: 1; background: #000000; color: #888888; }
240
+
241
+ .success { color: #1ED760; text-style: bold; }
242
+ .error { color: #ff4444; text-style: bold; }
243
+ .info { color: #1DB954; text-style: bold; }
244
+ .warning { color: #ffcc00; text-style: bold; }
245
+ .dim { color: #888888; }
246
+ """
247
+
248
+ def compose(self) -> ComposeResult:
249
+ yield ZeroHeader(id="zeroheader")
250
+ yield Dashboard(id="dashboard")
251
+
252
+ with Horizontal(id="main_area"):
253
+ with Vertical(id="left_column"):
254
+ with ScrollableContainer(id="setup_container"):
255
+ with Container(id="setup_panel"):
256
+ yield Label("⚙ SETUP", classes="info")
257
+
258
+ with Horizontal():
259
+ yield Label("Cookie Folder :")
260
+ yield Button("SELECT", id="btn_cookie_folder", variant="default")
261
+ yield Label("", id="lbl_cookie_folder", classes="dim")
262
+
263
+ with Horizontal():
264
+ yield Label("Output Folder :")
265
+ yield Button("SELECT", id="btn_output_folder", variant="default")
266
+ yield Label("", id="lbl_output_folder", classes="dim")
267
+
268
+ with Horizontal():
269
+ yield Label("Threads :")
270
+ yield Input(value="10", id="input_threads", type="integer")
271
+
272
+ with Horizontal():
273
+ yield Label("Proxy File :")
274
+ yield Button("SELECT", id="btn_proxy_file", variant="default")
275
+ yield Label("", id="lbl_proxy_file", classes="dim")
276
+
277
+ with Horizontal():
278
+ yield Label("Use Proxies :")
279
+ yield Checkbox("Disabled", id="chk_use_proxy")
280
+
281
+ with Container(id="start_button_container"):
282
+ with Horizontal():
283
+ yield Button("▶ START", id="btn_start", variant="success")
284
+ yield Button("⏹ STOP", id="btn_stop", variant="error")
285
+
286
+ yield LogPanel(id="log", markup=True, wrap=True)
287
+
288
+ yield Footer()
289
+
290
+ def on_mount(self) -> None:
291
+ # Show license screen first
292
+ self.push_screen(LicenseScreen(), callback=self.on_license_complete)
293
+
294
+ self.dashboard = self.query_one("#dashboard", Dashboard)
295
+ self.log_panel = self.query_one("#log", LogPanel)
296
+ self.btn_start = self.query_one("#btn_start", Button)
297
+ self.btn_stop = self.query_one("#btn_stop", Button)
298
+
299
+ self.lbl_cookie = self.query_one("#lbl_cookie_folder", Label)
300
+ self.lbl_output = self.query_one("#lbl_output_folder", Label)
301
+ self.lbl_proxy = self.query_one("#lbl_proxy_file", Label)
302
+ self.input_threads = self.query_one("#input_threads", Input)
303
+ self.chk_use_proxy = self.query_one("#chk_use_proxy", Checkbox)
304
+
305
+ self.cookie_folder = ""
306
+ self.output_base = ""
307
+ self.proxy_file = ""
308
+ self.proxy_list = []
309
+
310
+ self.total_files = 0
311
+ self.processed = 0
312
+ self.success_count = 0
313
+ self.fail_count = 0
314
+ self.start_time = None
315
+ self.running = False
316
+
317
+ self.btn_stop.disabled = True
318
+
319
+ self.log_panel.write("[dim]Welcome to ZeroChecker[/]")
320
+ self.log_panel.write("[dim]1. Select a cookie folder containing .txt cookie files.[/]")
321
+ self.log_panel.write("[dim]2. Select an output folder for results.[/]")
322
+ self.log_panel.write("[dim]3. (Optional) Select a proxy file and check the box to use proxies.[/]")
323
+ self.log_panel.write("[dim]4. Set threads and click '▶ START'. Use '⏹ STOP' or auto-429 protection to halt.[/]")
324
+ self.update_dashboard()
325
+
326
+ def on_license_complete(self, success: bool) -> None:
327
+ if success:
328
+ self.log_panel.write("[success]✅ License activated! Welcome.[/]")
329
+ else:
330
+ self.log_panel.write("[error]❌ License validation failed. Exiting.[/]")
331
+ self.exit()
332
+
333
+ def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
334
+ if event.checkbox.id == "chk_use_proxy":
335
+ event.checkbox.label = "Enabled" if event.value else "Disabled"
336
+
337
+ def on_button_pressed(self, event: Button.Pressed) -> None:
338
+ button_id = event.button.id
339
+
340
+ try:
341
+ if button_id == "btn_cookie_folder":
342
+ folder = gui_select_folder("Select folder with .txt cookie files")
343
+ if folder:
344
+ self.cookie_folder = folder
345
+ self.lbl_cookie.update(f"📁 {os.path.basename(folder)}")
346
+ self.log_panel.write(f"[info]Cookie folder set to: {folder}[/]")
347
+ else:
348
+ self.log_panel.write("[dim]Folder selection cancelled.[/]")
349
+
350
+ elif button_id == "btn_output_folder":
351
+ folder = gui_select_folder("Select output base folder")
352
+ if folder:
353
+ self.output_base = folder
354
+ self.lbl_output.update(f"📁 {os.path.basename(folder)}")
355
+ self.log_panel.write(f"[info]Output folder set to: {folder}[/]")
356
+ else:
357
+ self.log_panel.write("[dim]Output folder selection cancelled.[/]")
358
+
359
+ elif button_id == "btn_proxy_file":
360
+ filepath = gui_select_file("Select proxy file")
361
+ if filepath:
362
+ self.proxy_file = filepath
363
+ self.lbl_proxy.update(f"📄 {os.path.basename(filepath)}")
364
+ self.proxy_list = parse_proxy_file(filepath)
365
+ self.log_panel.write(f"[info]Loaded {len(self.proxy_list)} proxies.[/]")
366
+ else:
367
+ self.log_panel.write("[dim]File selection cancelled.[/]")
368
+
369
+ elif button_id == "btn_start":
370
+ self.start_checking()
371
+
372
+ elif button_id == "btn_stop":
373
+ self.log_panel.write("[warning]⏹ Manual stop requested! Halting all threads...[/]")
374
+ self.running = False
375
+
376
+ except Exception as exc:
377
+ self.log_panel.write(f"[error]UI error: {exc}[/]")
378
+
379
+ def update_dashboard(self):
380
+ if self.start_time:
381
+ elapsed_seconds = int(time.time() - self.start_time)
382
+ hours = elapsed_seconds // 3600
383
+ minutes = (elapsed_seconds % 3600) // 60
384
+ seconds = elapsed_seconds % 60
385
+ elapsed_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
386
+ else:
387
+ elapsed_str = "00:00:00"
388
+ rate = self.processed / (time.time() - self.start_time) if self.start_time and (time.time() - self.start_time) > 0 else 0.0
389
+ self.dashboard.total = self.total_files
390
+ self.dashboard.success = self.success_count
391
+ self.dashboard.failed = self.fail_count
392
+ self.dashboard.rate = rate
393
+ self.dashboard.output_dir = self.output_base
394
+ self.dashboard.proxies = len(self.proxy_list) if self.chk_use_proxy.value else 0
395
+ self.dashboard.elapsed = elapsed_str
396
+
397
+ def start_checking(self):
398
+ if not self.cookie_folder or not os.path.isdir(self.cookie_folder):
399
+ self.log_panel.write("[error]Please select a valid cookie folder.[/]")
400
+ return
401
+ if not self.output_base:
402
+ self.log_panel.write("[error]Please select an output folder.[/]")
403
+ return
404
+
405
+ try:
406
+ threads = int(self.input_threads.value)
407
+ if threads < 1:
408
+ raise ValueError
409
+ except Exception:
410
+ self.log_panel.write("[error]Please enter a valid number of threads.[/]")
411
+ return
412
+
413
+ use_proxy = self.chk_use_proxy.value
414
+ if use_proxy and not self.proxy_list:
415
+ self.log_panel.write("[warning]Proxy file selected but no proxies loaded. Continuing without proxies.[/]")
416
+ use_proxy = False
417
+
418
+ proxy_list = self.proxy_list if use_proxy else []
419
+
420
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
421
+ output_dir = os.path.join(self.output_base, timestamp)
422
+ os.makedirs(output_dir, exist_ok=True)
423
+ os.makedirs(os.path.join(output_dir, "not_working"), exist_ok=True)
424
+ self.output_base = output_dir
425
+
426
+ cookie_files = [f for f in os.listdir(self.cookie_folder) if f.endswith(".txt")]
427
+ self.total_files = len(cookie_files)
428
+ if self.total_files == 0:
429
+ self.log_panel.write("[error]No .txt files found in the cookie folder.[/]")
430
+ return
431
+
432
+ self.btn_start.disabled = True
433
+ self.btn_start.label = "⏳ RUNNING"
434
+ self.btn_stop.disabled = False
435
+ self.running = True
436
+ self.start_time = time.time()
437
+ self.processed = 0
438
+ self.success_count = 0
439
+ self.fail_count = 0
440
+ self.update_dashboard()
441
+
442
+ self.log_panel.write(f"[info]Starting with {threads} threads, {len(proxy_list)} proxies.[/]")
443
+ self.log_panel.write(f"[info]Output directory: {output_dir}[/]")
444
+
445
+ threading.Thread(target=self.run_checker, args=(cookie_files, threads, proxy_list), daemon=True).start()
446
+
447
+ def _finish_ui(self):
448
+ self.btn_start.disabled = False
449
+ self.btn_start.label = "▶ START"
450
+ self.btn_stop.disabled = True
451
+ self.log_panel.write("[info]✅ Process halted/completed.[/]")
452
+ self.update_dashboard()
453
+
454
+ def run_checker(self, cookie_files, threads, proxy_list):
455
+ with ThreadPoolExecutor(max_workers=threads) as executor:
456
+ futures = {}
457
+ for idx, fname in enumerate(cookie_files):
458
+ filepath = os.path.join(self.cookie_folder, fname)
459
+ proxy = proxy_list[idx % len(proxy_list)] if proxy_list else None
460
+ future = executor.submit(process_one_cookie, filepath, self.output_base, proxy)
461
+ futures[future] = fname
462
+
463
+ for future in as_completed(futures):
464
+ if not self.running:
465
+ break
466
+ fname = futures[future]
467
+ try:
468
+ ok, username, plan, err, content = future.result()
469
+ except Exception as e:
470
+ ok, username, plan, err, content = False, None, None, f"Exception: {str(e)}", None
471
+
472
+ if err and "429" in err:
473
+ self.call_from_thread(self.log_panel.write, f"[warning]⚠️ 429 Rate Limit detected on {fname}! Halting all threads immediately to protect your setup.[/]")
474
+ self.running = False
475
+
476
+ if ok:
477
+ self.success_count += 1
478
+ self.call_from_thread(self.log_panel.write, f"[success]✅ {fname} -> {username} ({plan})[/]")
479
+ safe_plan = "".join(c for c in plan if c.isalnum() or c in " _-").strip()
480
+ if not safe_plan: safe_plan = "UnknownPlan"
481
+ plan_folder = os.path.join(self.output_base, "plans", safe_plan)
482
+ os.makedirs(plan_folder, exist_ok=True)
483
+ out_path = os.path.join(plan_folder, f"{plan}_{username}.txt")
484
+ with open(out_path, "w", encoding="utf-8") as f: f.write(content)
485
+ else:
486
+ self.fail_count += 1
487
+ self.call_from_thread(self.log_panel.write, f"[error]❌ {fname} -> FAILED: {err}[/]")
488
+ fail_file = os.path.join(self.output_base, "not_working", f"{fname}.txt")
489
+ try:
490
+ with open(os.path.join(self.cookie_folder, fname), 'r', encoding='utf-8') as orig:
491
+ with open(fail_file, "w", encoding="utf-8") as ff: ff.write(orig.read())
492
+ except Exception:
493
+ with open(fail_file, "w", encoding="utf-8") as ff: ff.write(f"Original content unavailable.\nError: {err}")
494
+
495
+ self.processed += 1
496
+ self.call_from_thread(self.update_dashboard)
497
+
498
+ if not self.running:
499
+ break
500
+
501
+ executor.shutdown(wait=False, cancel_futures=True)
502
+
503
+ self.running = False
504
+ self.call_from_thread(self._finish_ui)
505
+
506
+
507
+ def main():
508
+ """Entry point for the zerocheck command."""
509
+ resize_terminal(rows=34, cols=110)
510
+ app = ZeroCheckerApp()
511
+ app.run()
512
+
513
+
514
+ if __name__ == "__main__":
515
+ main()
@@ -0,0 +1,162 @@
1
+ """Keygen.sh license validation with hardware fingerprinting"""
2
+
3
+ import platform
4
+ import uuid
5
+ import hashlib
6
+ import requests
7
+ from .config import KEYGEN_ACCOUNT_ID, KEYGEN_ADMIN_TOKEN, KEYGEN_API_URL
8
+
9
+
10
+ def get_hardware_fingerprint() -> str:
11
+ """Generate a unique hardware fingerprint from multiple system identifiers."""
12
+ identifiers = []
13
+
14
+ # MAC Address
15
+ try:
16
+ mac = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff)
17
+ for i in range(0, 2*6, 8)][::-1])
18
+ identifiers.append(f"MAC:{mac}")
19
+ except:
20
+ pass
21
+
22
+ # Computer Name
23
+ identifiers.append(f"PC:{platform.node()}")
24
+
25
+ # Processor
26
+ identifiers.append(f"CPU:{platform.processor()}")
27
+
28
+ # Platform/OS
29
+ identifiers.append(f"OS:{platform.platform()}")
30
+
31
+ # Combine and hash all identifiers
32
+ combined = '|'.join(identifiers)
33
+ fingerprint = hashlib.sha256(combined.encode()).hexdigest()
34
+
35
+ return fingerprint
36
+
37
+
38
+ def validate_license_key(license_key: str) -> tuple[bool, str]:
39
+ """Validate license key with hardware fingerprint using admin token."""
40
+ fingerprint = get_hardware_fingerprint()
41
+
42
+ try:
43
+ # Validate the license key
44
+ response = requests.post(
45
+ f"{KEYGEN_API_URL}/accounts/{KEYGEN_ACCOUNT_ID}/licenses/actions/validate-key",
46
+ headers={
47
+ "Authorization": f"Bearer {KEYGEN_ADMIN_TOKEN}",
48
+ "Content-Type": "application/vnd.api+json",
49
+ "Accept": "application/vnd.api+json"
50
+ },
51
+ json={
52
+ "meta": {
53
+ "key": license_key,
54
+ "scope": {
55
+ "fingerprint": fingerprint
56
+ }
57
+ }
58
+ },
59
+ timeout=10
60
+ )
61
+
62
+ data = response.json()
63
+
64
+ # Check if valid
65
+ if data.get("meta", {}).get("valid", False):
66
+ return True, "License validated successfully"
67
+ else:
68
+ code = data.get("meta", {}).get("code", "INVALID")
69
+
70
+ # Handle different validation codes
71
+ if code in ["NO_MACHINE", "NO_MACHINES", "FINGERPRINT_SCOPE_MISMATCH"]:
72
+ # First time using this key on this hardware - activate it
73
+ return activate_license(license_key, fingerprint)
74
+ elif code == "OVERDUE":
75
+ return False, "License is overdue"
76
+ elif code == "EXPIRED":
77
+ return False, "License has expired"
78
+ elif code == "SUSPENDED":
79
+ return False, "License is suspended"
80
+ elif code == "TOO_MANY_MACHINES":
81
+ return False, "License already in use on another device"
82
+ else:
83
+ return False, f"License validation failed: {code}"
84
+
85
+ except requests.exceptions.Timeout:
86
+ return False, "Connection timeout. Check your internet."
87
+ except requests.exceptions.ConnectionError:
88
+ return False, "Cannot connect to license server"
89
+ except Exception as e:
90
+ return False, f"Validation error: {str(e)}"
91
+
92
+
93
+ def activate_license(license_key: str, fingerprint: str) -> tuple[bool, str]:
94
+ """Activate license for this hardware (first-time use)."""
95
+ try:
96
+ # First, get the license ID using admin token
97
+ response = requests.get(
98
+ f"{KEYGEN_API_URL}/accounts/{KEYGEN_ACCOUNT_ID}/licenses",
99
+ headers={
100
+ "Authorization": f"Bearer {KEYGEN_ADMIN_TOKEN}",
101
+ "Accept": "application/vnd.api+json"
102
+ },
103
+ params={
104
+ "key": license_key
105
+ },
106
+ timeout=10
107
+ )
108
+
109
+ if response.status_code != 200:
110
+ return False, "Invalid license key"
111
+
112
+ data = response.json()
113
+ licenses = data.get("data", [])
114
+
115
+ if not licenses:
116
+ return False, "License key not found"
117
+
118
+ license_id = licenses[0]["id"]
119
+
120
+ # Activate the license for this machine
121
+ response = requests.post(
122
+ f"{KEYGEN_API_URL}/accounts/{KEYGEN_ACCOUNT_ID}/machines",
123
+ headers={
124
+ "Authorization": f"Bearer {KEYGEN_ADMIN_TOKEN}",
125
+ "Content-Type": "application/vnd.api+json",
126
+ "Accept": "application/vnd.api+json"
127
+ },
128
+ json={
129
+ "data": {
130
+ "type": "machines",
131
+ "attributes": {
132
+ "fingerprint": fingerprint,
133
+ "name": platform.node()
134
+ },
135
+ "relationships": {
136
+ "license": {
137
+ "data": {
138
+ "type": "licenses",
139
+ "id": license_id
140
+ }
141
+ }
142
+ }
143
+ }
144
+ },
145
+ timeout=10
146
+ )
147
+
148
+ if response.status_code in [200, 201]:
149
+ return True, "License activated for this device"
150
+ elif response.status_code == 422:
151
+ error_data = response.json()
152
+ detail = error_data.get("errors", [{}])[0].get("detail", "Activation failed")
153
+ return False, f"Activation failed: {detail}"
154
+ else:
155
+ return False, f"Activation failed with status {response.status_code}"
156
+
157
+ except requests.exceptions.Timeout:
158
+ return False, "Connection timeout during activation"
159
+ except requests.exceptions.ConnectionError:
160
+ return False, "Cannot connect to license server"
161
+ except Exception as e:
162
+ return False, f"Activation error: {str(e)}"
@@ -0,0 +1,139 @@
1
+ """Spotify cookie checking backend"""
2
+
3
+ import os
4
+ import re
5
+ import json
6
+ import requests
7
+ from bs4 import BeautifulSoup
8
+ from urllib.parse import urlparse
9
+ from .config import SPOTIFY_URL, USER_AGENT, TIMEOUT
10
+
11
+
12
+ def parse_cookie_file(filepath):
13
+ """Bulletproof manual cookie parser. No strict header requirements."""
14
+ session = requests.Session()
15
+ session.headers.update({
16
+ "User-Agent": USER_AGENT,
17
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
18
+ "Accept-Language": "en-US,en;q=0.9",
19
+ })
20
+ adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)
21
+ session.mount('https://', adapter)
22
+ session.mount('http://', adapter)
23
+
24
+ try:
25
+ with open(filepath, 'r', encoding='utf-8') as f:
26
+ for line in f:
27
+ line = line.strip()
28
+ if not line or line.startswith('#'):
29
+ continue
30
+ parts = line.split('\t')
31
+ if len(parts) >= 7:
32
+ domain = parts[0]
33
+ path = parts[2]
34
+ secure_flag = parts[3].upper() == 'TRUE'
35
+ name = parts[5]
36
+ value = parts[6]
37
+ session.cookies.set(name, value, domain=domain, path=path, secure=secure_flag)
38
+ except Exception:
39
+ return None
40
+
41
+ if len(session.cookies) == 0:
42
+ return None
43
+
44
+ return session
45
+
46
+
47
+ def extract_info(html):
48
+ """Extract username and plan from Spotify account page."""
49
+ soup = BeautifulSoup(html, "html.parser")
50
+ username = None
51
+ constants = soup.find("div", id="constants")
52
+ if constants and constants.has_attr("data-username"):
53
+ username = constants["data-username"]
54
+ plan = "Unknown"
55
+ plan_elem = soup.find(attrs={"data-testid": "plan-name-title"})
56
+ if plan_elem:
57
+ plan = plan_elem.get_text(strip=True)
58
+ else:
59
+ next_data = soup.find("script", id="__NEXT_DATA__")
60
+ if next_data and next_data.string:
61
+ try:
62
+ data = json.loads(next_data.string)
63
+ widgets = data.get("props", {}).get("pageProps", {}).get("dynamicWidgets", {})
64
+ plan_cards = widgets.get("PlanCard", [])
65
+ for card in plan_cards:
66
+ if "props" in card and "planName" in card["props"]:
67
+ plan = card["props"]["planName"]
68
+ break
69
+ except Exception:
70
+ pass
71
+ if not plan or plan == "":
72
+ match = re.search(r'"planName"\s*:\s*"([^"]+)"', html)
73
+ if match:
74
+ plan = match.group(1)
75
+ return username, plan
76
+
77
+
78
+ def is_logged_in(html):
79
+ """Check if the HTML indicates a logged-in state."""
80
+ if re.search(r'data-anonymous\s*=\s*"false"', html): return True
81
+ if re.search(r'"loggedIn"\s*:\s*true', html): return True
82
+ if re.search(r'data-username\s*=\s*"[^"]+"', html): return True
83
+ if re.search(r'<title>Account Overview - Spotify</title>', html): return True
84
+ if re.search(r'<form[^>]*action="[^"]*login[^"]*"', html, re.IGNORECASE): return False
85
+ return False
86
+
87
+
88
+ def process_one_cookie(filepath, output_base, proxy=None):
89
+ """Process a single cookie file and check if it's valid."""
90
+ filename = os.path.basename(filepath)
91
+ session = parse_cookie_file(filepath)
92
+ if session is None:
93
+ return False, None, None, "Failed to parse cookies", None
94
+ if proxy:
95
+ session.proxies = {"http": proxy, "https": proxy}
96
+
97
+ try:
98
+ resp = session.get(SPOTIFY_URL, timeout=TIMEOUT, allow_redirects=True)
99
+ if resp.status_code == 429:
100
+ return False, None, None, "429 Too Many Requests (Rate Limited)", None
101
+ resp.raise_for_status()
102
+ except requests.exceptions.HTTPError as e:
103
+ if "429" in str(e):
104
+ return False, None, None, "429 Too Many Requests (Rate Limited)", None
105
+ return False, None, None, f"Request error: {str(e)}", None
106
+ except Exception as e:
107
+ return False, None, None, f"Request error: {str(e)}", None
108
+
109
+ html = resp.text
110
+ if not is_logged_in(html):
111
+ return False, None, None, "Not logged in", None
112
+
113
+ username, plan = extract_info(html)
114
+ if not username:
115
+ debug_dir = os.path.join(output_base, "debug")
116
+ os.makedirs(debug_dir, exist_ok=True)
117
+ with open(os.path.join(debug_dir, f"{filename}.html"), "w", encoding="utf-8") as f:
118
+ f.write(html)
119
+ return False, None, None, "Could not extract username", None
120
+
121
+ with open(filepath, 'r', encoding='utf-8') as f:
122
+ original_content = f.read()
123
+ return True, username, plan, None, original_content
124
+
125
+
126
+ def parse_proxy_file(filepath):
127
+ """Parse proxy file and return list of proxies."""
128
+ proxies = []
129
+ with open(filepath, 'r', encoding='utf-8') as f:
130
+ for line in f:
131
+ line = line.strip()
132
+ if not line or line.startswith('#'): continue
133
+ if '://' not in line: line = 'http://' + line
134
+ try:
135
+ parsed = urlparse(line)
136
+ if parsed.scheme not in ('http', 'https', 'socks5'): line = 'http://' + line
137
+ proxies.append(line)
138
+ except Exception: continue
139
+ return proxies
@@ -0,0 +1,11 @@
1
+ """Configuration constants for ZeroChecker"""
2
+
3
+ # Keygen.sh Configuration
4
+ KEYGEN_ACCOUNT_ID = "ea5034cf-1ed4-4d54-b08c-1d406dea1d99" # ← Replace with your Keygen Account ID
5
+ KEYGEN_ADMIN_TOKEN = "admin-124b528ab6edf7c3342da4d64bbd18cf850875483e8f95b13a5df9367b4610e9v3" # ← Replace with your admin token
6
+ KEYGEN_API_URL = "https://api.keygen.sh/v1"
7
+
8
+ # Spotify Checker Configuration
9
+ SPOTIFY_URL = "https://www.spotify.com/in-en/account/overview/?utm_source=spotify&utm_medium=menu&utm_campaign=your_account"
10
+ USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
11
+ TIMEOUT = 10
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: zerocheck
3
+ Version: 1.0.0
4
+ Summary: Blazing fast Spotify Cookie Checker with hardware-locked licensing
5
+ Author-email: Zyco <zyco@zyco1.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: textual>=0.40.0
9
+ Requires-Dist: requests>=2.28.0
10
+ Requires-Dist: beautifulsoup4>=4.11.0
11
+
12
+ # ZeroChecker 🎧
13
+
14
+ Blazing fast Spotify Cookie Checker with a sleek terminal UI and hardware-locked licensing.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install zerocheck
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ zerocheck/__init__.py
4
+ zerocheck/__main__.py
5
+ zerocheck/app.py
6
+ zerocheck/auth.py
7
+ zerocheck/backend.py
8
+ zerocheck/config.py
9
+ zerocheck.egg-info/PKG-INFO
10
+ zerocheck.egg-info/SOURCES.txt
11
+ zerocheck.egg-info/dependency_links.txt
12
+ zerocheck.egg-info/entry_points.txt
13
+ zerocheck.egg-info/requires.txt
14
+ zerocheck.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ zerocheck = zerocheck.app:main
@@ -0,0 +1,3 @@
1
+ textual>=0.40.0
2
+ requests>=2.28.0
3
+ beautifulsoup4>=4.11.0
@@ -0,0 +1 @@
1
+ zerocheck