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.
- zerocheck-1.0.0/PKG-INFO +19 -0
- zerocheck-1.0.0/README.md +8 -0
- zerocheck-1.0.0/pyproject.toml +25 -0
- zerocheck-1.0.0/setup.cfg +4 -0
- zerocheck-1.0.0/zerocheck/__init__.py +2 -0
- zerocheck-1.0.0/zerocheck/__main__.py +5 -0
- zerocheck-1.0.0/zerocheck/app.py +515 -0
- zerocheck-1.0.0/zerocheck/auth.py +162 -0
- zerocheck-1.0.0/zerocheck/backend.py +139 -0
- zerocheck-1.0.0/zerocheck/config.py +11 -0
- zerocheck-1.0.0/zerocheck.egg-info/PKG-INFO +19 -0
- zerocheck-1.0.0/zerocheck.egg-info/SOURCES.txt +14 -0
- zerocheck-1.0.0/zerocheck.egg-info/dependency_links.txt +1 -0
- zerocheck-1.0.0/zerocheck.egg-info/entry_points.txt +2 -0
- zerocheck-1.0.0/zerocheck.egg-info/requires.txt +3 -0
- zerocheck-1.0.0/zerocheck.egg-info/top_level.txt +1 -0
zerocheck-1.0.0/PKG-INFO
ADDED
|
@@ -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,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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zerocheck
|