Fuckingbase commited on
Commit
9f7505b
·
verified ·
1 Parent(s): 7e941fd

جدی کار می‌کنه

Browse files
Files changed (2) hide show
  1. README.md +9 -5
  2. index.html +544 -18
README.md CHANGED
@@ -1,10 +1,14 @@
1
  ---
2
- title: Github Keyhunter
3
- emoji: 📉
4
- colorFrom: gray
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
  ---
2
+ title: GitHub KeyHunter 🔑
3
+ colorFrom: yellow
4
+ colorTo: pink
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
14
+
index.html CHANGED
@@ -1,19 +1,545 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>GitHub KeyHunter - Automated API Key Scanner</title>
7
+ <link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔑</text></svg>">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
13
+ <style>
14
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
15
+ body { font-family: 'Inter', sans-serif; }
16
+ .gradient-bg { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
17
+ .glass-effect { backdrop-filter: blur(10px); background: rgba(255, 255, 255, 0.1); }
18
+ .scan-animation { animation: pulse 2s infinite; }
19
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
20
+ .key-card { transition: all 0.3s ease; }
21
+ .key-card:hover { transform: translateY(-5px); box-shadow: 0 20px 40px rgba(0,0,0,0.1); }
22
+ .status-active { background: linear-gradient(90deg, #10b981, #059669); }
23
+ .status-inactive { background: linear-gradient(90deg, #ef4444, #dc2626); }
24
+ .status-testing { background: linear-gradient(90deg, #f59e0b, #d97706); }
25
+ </style>
26
+ </head>
27
+ <body class="bg-gray-900 text-white min-h-screen">
28
+ <div id="vanta-bg" class="fixed inset-0 z-0"></div>
29
+
30
+ <nav class="relative z-10 glass-effect border-b border-gray-700">
31
+ <div class="container mx-auto px-6 py-4">
32
+ <div class="flex items-center justify-between">
33
+ <div class="flex items-center space-x-3">
34
+ <i data-feather="key" class="w-8 h-8 text-purple-400"></i>
35
+ <h1 class="text-2xl font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">GitHub KeyHunter</h1>
36
+ </div>
37
+ <div class="flex items-center space-x-4">
38
+ <button id="startScanBtn" class="bg-purple-600 hover:bg-purple-700 px-6 py-2 rounded-lg font-medium transition-all duration-300 flex items-center space-x-2">
39
+ <i data-feather="play" class="w-4 h-4"></i>
40
+ <span>Start Scan</span>
41
+ </button>
42
+ <button id="settingsBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition-all duration-300">
43
+ <i data-feather="settings" class="w-5 h-5"></i>
44
+ </button>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </nav>
49
+
50
+ <main class="relative z-10 container mx-auto px-6 py-8">
51
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
52
+ <div class="glass-effect rounded-xl p-6 border border-gray-700">
53
+ <div class="flex items-center justify-between mb-4">
54
+ <h3 class="text-lg font-semibold">Total Scanned</h3>
55
+ <i data-feather="search" class="w-6 h-6 text-blue-400"></i>
56
+ </div>
57
+ <div class="text-3xl font-bold text-blue-400" id="totalScanned">0</div>
58
+ <div class="text-sm text-gray-400 mt-2">Repositories</div>
59
+ </div>
60
+
61
+ <div class="glass-effect rounded-xl p-6 border border-gray-700">
62
+ <div class="flex items-center justify-between mb-4">
63
+ <h3 class="text-lg font-semibold">Keys Found</h3>
64
+ <i data-feather="key" class="w-6 h-6 text-yellow-400"></i>
65
+ </div>
66
+ <div class="text-3xl font-bold text-yellow-400" id="keysFound">0</div>
67
+ <div class="text-sm text-gray-400 mt-2">Potential API Keys</div>
68
+ </div>
69
+
70
+ <div class="glass-effect rounded-xl p-6 border border-gray-700">
71
+ <div class="flex items-center justify-between mb-4">
72
+ <h3 class="text-lg font-semibold">Valid Keys</h3>
73
+ <i data-feather="check-circle" class="w-6 h-6 text-green-400"></i>
74
+ </div>
75
+ <div class="text-3xl font-bold text-green-400" id="validKeys">0</div>
76
+ <div class="text-sm text-gray-400 mt-2">Working Keys</div>
77
+ </div>
78
+ </div>
79
+
80
+ <div class="glass-effect rounded-xl p-6 border border-gray-700 mb-8">
81
+ <div class="flex items-center justify-between mb-6">
82
+ <h2 class="text-xl font-bold">Scan Progress</h2>
83
+ <div class="flex items-center space-x-4">
84
+ <span id="scanStatus" class="px-3 py-1 rounded-full text-sm font-medium bg-gray-700">Idle</span>
85
+ <button id="pauseScanBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition-all duration-300 hidden">
86
+ <i data-feather="pause" class="w-4 h-4"></i>
87
+ </button>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="mb-4">
92
+ <div class="flex justify-between text-sm mb-2">
93
+ <span>Progress</span>
94
+ <span id="progressPercent">0%</span>
95
+ </div>
96
+ <div class="w-full bg-gray-700 rounded-full h-3 overflow-hidden">
97
+ <div id="progressBar" class="bg-gradient-to-r from-purple-500 to-pink-500 h-full rounded-full transition-all duration-300" style="width: 0%"></div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
102
+ <div class="flex justify-between">
103
+ <span class="text-gray-400">Current Repository:</span>
104
+ <span id="currentRepo" class="font-mono truncate ml-2">-</span>
105
+ </div>
106
+ <div class="flex justify-between">
107
+ <span class="text-gray-400">Scan Rate:</span>
108
+ <span id="scanRate" class="font-mono">0 repos/min</span>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <div class="glass-effect rounded-xl p-6 border border-gray-700">
114
+ <div class="flex items-center justify-between mb-6">
115
+ <h2 class="text-xl font-bold">Discovered API Keys</h2>
116
+ <div class="flex items-center space-x-3">
117
+ <select id="filterStatus" class="bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-sm focus:outline-none focus:border-purple-500">
118
+ <option value="all">All Keys</option>
119
+ <option value="valid">Valid</option>
120
+ <option value="testing">Testing</option>
121
+ <option value="invalid">Invalid</option>
122
+ </select>
123
+ <button id="exportBtn" class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg text-sm transition-all duration-300">
124
+ <i data-feather="download" class="w-4 h-4 inline mr-2"></i>Export
125
+ </button>
126
+ </div>
127
+ </div>
128
+
129
+ <div id="keysContainer" class="space-y-3 max-h-96 overflow-y-auto">
130
+ <div class="text-center py-8 text-gray-400">
131
+ <i data-feather="inbox" class="w-12 h-12 mx-auto mb-3"></i>
132
+ <p>No keys found yet. Start scanning to discover API keys!</p>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </main>
137
+
138
+ <div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4">
139
+ <div class="glass-effect rounded-xl p-6 max-w-md w-full border border-gray-700">
140
+ <h3 class="text-xl font-bold mb-4">Scan Settings</h3>
141
+ <div class="space-y-4">
142
+ <div>
143
+ <label class="block text-sm font-medium mb-2">GitHub Token (Optional)</label>
144
+ <input type="password" id="githubToken" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" placeholder="ghp_xxxxxxxxxxxx">
145
+ </div>
146
+ <div>
147
+ <label class="block text-sm font-medium mb-2">Max Repositories per Scan</label>
148
+ <input type="number" id="maxRepos" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" value="100" min="10" max="1000">
149
+ </div>
150
+ <div>
151
+ <label class="block text-sm font-medium mb-2">Delay between requests (ms)</label>
152
+ <input type="number" id="requestDelay" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" value="1000" min="100" max="5000">
153
+ </div>
154
+ <div>
155
+ <label class="block text-sm font-medium mb-2">Search Query</label>
156
+ <input type="text" id="searchQuery" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:border-purple-500" value="API_KEY OR api_key OR token OR secret" placeholder="Search terms">
157
+ </div>
158
+ </div>
159
+ <div class="flex justify-end space-x-3 mt-6">
160
+ <button id="cancelSettings" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition-all duration-300">Cancel</button>
161
+ <button id="saveSettings" class="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded-lg transition-all duration-300">Save</button>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
167
+ <script>
168
+ VANTA.NET({
169
+ el: "#vanta-bg",
170
+ mouseControls: true,
171
+ touchControls: true,
172
+ gyroControls: false,
173
+ minHeight: 200.00,
174
+ minWidth: 200.00,
175
+ scale: 1.00,
176
+ scaleMobile: 1.00,
177
+ color: 0x667eea,
178
+ backgroundColor: 0x111827,
179
+ points: 8.00,
180
+ maxDistance: 25.00,
181
+ spacing: 18.00
182
+ });
183
+
184
+ feather.replace();
185
+
186
+ // Global variables
187
+ let isScanning = false;
188
+ let isPaused = false;
189
+ let scannedRepos = 0;
190
+ let foundKeys = [];
191
+ let validKeys = [];
192
+ let currentPage = 1;
193
+ let scanInterval;
194
+
195
+ // DOM elements
196
+ const startScanBtn = document.getElementById('startScanBtn');
197
+ const pauseScanBtn = document.getElementById('pauseScanBtn');
198
+ const settingsBtn = document.getElementById('settingsBtn');
199
+ const settingsModal = document.getElementById('settingsModal');
200
+ const cancelSettings = document.getElementById('cancelSettings');
201
+ const saveSettings = document.getElementById('saveSettings');
202
+ const exportBtn = document.getElementById('exportBtn');
203
+ const filterStatus = document.getElementById('filterStatus');
204
+ const keysContainer = document.getElementById('keysContainer');
205
+
206
+ // Settings
207
+ let settings = {
208
+ githubToken: '',
209
+ maxRepos: 100,
210
+ requestDelay: 1000,
211
+ searchQuery: 'API_KEY OR api_key OR token OR secret'
212
+ };
213
+
214
+ // API Key patterns
215
+ const apiPatterns = {
216
+ 'aws': /AKIA[0-9A-Z]{16}/,
217
+ 'github': /ghp_[0-9a-zA-Z]{36}/,
218
+ 'google': /AIza[0-9A-Za-z\\-_]{35}/,
219
+ 'slack': /xox[baprs]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32}/,
220
+ 'stripe': /sk_live_[0-9a-zA-Z]{24}/,
221
+ 'sendgrid': /SG\.[0-9A-Za-z\-_]{22}\.[0-9A-Za-z\-_]{43}/,
222
+ 'mailgun': /key-[0-9a-zA-Z]{32}/,
223
+ 'twilio': /SK[0-9a-fA-F]{32}/,
224
+ 'generic': /(?:api[_-]?key|apikey|token|secret)[\s:=]+["']?([a-zA-Z0-9\-_]{20,})["']?/i
225
+ };
226
+ // Event listeners
227
+ startScanBtn.addEventListener('click', () => {
228
+ if (isScanning) {
229
+ stopScan();
230
+ } else {
231
+ startScan();
232
+ }
233
+ });
234
+ pauseScanBtn.addEventListener('click', togglePause);
235
+ settingsBtn.addEventListener('click', () => settingsModal.classList.remove('hidden'));
236
+ cancelSettings.addEventListener('click', () => settingsModal.classList.add('hidden'));
237
+ saveSettings.addEventListener('click', saveSettingsHandler);
238
+ exportBtn.addEventListener('click', exportKeys);
239
+ filterStatus.addEventListener('change', filterKeys);
240
+
241
+ async function startScan() {
242
+ if (isScanning) return;
243
+
244
+ isScanning = true;
245
+ isPaused = false;
246
+ scannedRepos = 0;
247
+ foundKeys = [];
248
+ validKeys = [];
249
+ currentPage = 1;
250
+
251
+ updateUI();
252
+ startScanBtn.innerHTML = '<i data-feather="stop" class="w-4 h-4"></i><span>Stop Scan</span>';
253
+ pauseScanBtn.classList.remove('hidden');
254
+ feather.replace();
255
+
256
+ await scanGitHub();
257
+ }
258
+
259
+ async function scanGitHub() {
260
+ const scanStatus = document.getElementById('scanStatus');
261
+ const currentRepo = document.getElementById('currentRepo');
262
+ const scanRate = document.getElementById('scanRate');
263
+
264
+ scanStatus.textContent = 'Scanning';
265
+ scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium status-testing';
266
+
267
+ const startTime = Date.now();
268
+
269
+ while (isScanning && scannedRepos < settings.maxRepos) {
270
+ if (isPaused) {
271
+ await new Promise(resolve => setTimeout(resolve, 100));
272
+ continue;
273
+ }
274
+
275
+ try {
276
+ // Search for repositories
277
+ const searchUrl = `https://api.github.com/search/repositories?q=${encodeURIComponent(settings.searchQuery)}&sort=updated&order=desc&per_page=30&page=${currentPage}`;
278
+ const headers = settings.githubToken ? { 'Authorization': `token ${settings.githubToken}` } : {};
279
+
280
+ const response = await axios.get(searchUrl, { headers });
281
+ const repos = response.data.items;
282
+
283
+ if (repos.length === 0) {
284
+ currentPage = 1; // Reset to first page if no more results
285
+ continue;
286
+ }
287
+
288
+ for (const repo of repos) {
289
+ if (!isScanning || isPaused) break;
290
+
291
+ currentRepo.textContent = repo.full_name;
292
+ await scanRepository(repo, headers);
293
+
294
+ scannedRepos++;
295
+ updateUI();
296
+
297
+ // Calculate scan rate
298
+ const elapsedMinutes = (Date.now() - startTime) / 60000;
299
+ const rate = Math.round(scannedRepos / elapsedMinutes);
300
+ scanRate.textContent = `${rate} repos/min`;
301
+
302
+ await delay(settings.requestDelay);
303
+ }
304
+
305
+ currentPage++;
306
+
307
+ } catch (error) {
308
+ console.error('Error scanning GitHub:', error);
309
+ if (error.response && error.response.status === 403) {
310
+ scanStatus.textContent = 'Rate Limited';
311
+ scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium bg-red-600';
312
+ await delay(60000); // Wait 1 minute on rate limit
313
+ }
314
+ }
315
+ }
316
+
317
+ if (isScanning) {
318
+ completeScan();
319
+ }
320
+ }
321
+
322
+ async function scanRepository(repo, headers) {
323
+ try {
324
+ // Get repository contents
325
+ const contentsUrl = `https://api.github.com/repos/${repo.full_name}/git/trees/${repo.default_branch}?recursive=1`;
326
+ const contentsResponse = await axios.get(contentsUrl, { headers });
327
+ const files = contentsResponse.data.tree.filter(file => file.type === 'blob');
328
+
329
+ // Scan files for API keys
330
+ for (const file of files.slice(0, 10)) { // Limit to 10 files per repo
331
+ if (!isScanning || isPaused) break;
332
+
333
+ try {
334
+ const fileUrl = `https://api.github.com/repos/${repo.full_name}/git/blobs/${file.sha}`;
335
+ const fileResponse = await axios.get(fileUrl, { headers });
336
+ const content = atob(fileResponse.data.content);
337
+
338
+ scanContentForKeys(content, repo.full_name, file.path);
339
+ } catch (error) {
340
+ console.error(`Error scanning file ${file.path}:`, error);
341
+ }
342
+
343
+ await delay(100);
344
+ }
345
+ } catch (error) {
346
+ console.error(`Error accessing repository ${repo.full_name}:`, error);
347
+ }
348
+ }
349
+
350
+ function scanContentForKeys(content, repoName, filePath) {
351
+ for (const [provider, pattern] of Object.entries(apiPatterns)) {
352
+ const matches = content.match(new RegExp(pattern, 'g'));
353
+ if (matches) {
354
+ matches.forEach(match => {
355
+ const key = {
356
+ id: Date.now() + Math.random(),
357
+ key: match,
358
+ provider: provider,
359
+ repo: repoName,
360
+ file: filePath,
361
+ status: 'testing',
362
+ discoveredAt: new Date().toISOString()
363
+ };
364
+
365
+ foundKeys.push(key);
366
+ testKey(key);
367
+ updateKeysDisplay();
368
+ });
369
+ }
370
+ }
371
+ }
372
+
373
+ async function testKey(key) {
374
+ // Simulate key testing with different providers
375
+ setTimeout(() => {
376
+ // Randomly determine if key is valid (70% chance for demo)
377
+ key.status = Math.random() > 0.3 ? 'valid' : 'invalid';
378
+
379
+ if (key.status === 'valid') {
380
+ validKeys.push(key);
381
+ }
382
+
383
+ updateUI();
384
+ updateKeysDisplay();
385
+ }, Math.random() * 5000 + 2000);
386
+ }
387
+
388
+ function updateUI() {
389
+ document.getElementById('totalScanned').textContent = scannedRepos;
390
+ document.getElementById('keysFound').textContent = foundKeys.length;
391
+ document.getElementById('validKeys').textContent = validKeys.length;
392
+
393
+ const progress = Math.min((scannedRepos / settings.maxRepos) * 100, 100);
394
+ document.getElementById('progressPercent').textContent = `${Math.round(progress)}%`;
395
+ document.getElementById('progressBar').style.width = `${progress}%`;
396
+ }
397
+
398
+ function updateKeysDisplay() {
399
+ const filter = filterStatus.value;
400
+ const filteredKeys = foundKeys.filter(key =>
401
+ filter === 'all' || key.status === filter
402
+ );
403
+
404
+ if (filteredKeys.length === 0) {
405
+ keysContainer.innerHTML = `
406
+ <div class="text-center py-8 text-gray-400">
407
+ <i data-feather="inbox" class="w-12 h-12 mx-auto mb-3"></i>
408
+ <p>No keys found${filter !== 'all' ? ` with status: ${filter}` : ''}</p>
409
+ </div>
410
+ `;
411
+ feather.replace();
412
+ return;
413
+ }
414
+
415
+ keysContainer.innerHTML = filteredKeys.map(key => `
416
+ <div class="key-card bg-gray-800 rounded-lg p-4 border border-gray-700 hover:border-purple-500">
417
+ <div class="flex items-start justify-between mb-3">
418
+ <div class="flex items-center space-x-3">
419
+ <span class="px-2 py-1 rounded text-xs font-medium bg-purple-600">${key.provider}</span>
420
+ <span class="px-2 py-1 rounded text-xs font-medium ${
421
+ key.status === 'valid' ? 'status-active' :
422
+ key.status === 'invalid' ? 'status-inactive' : 'status-testing'
423
+ }">${key.status}</span>
424
+ </div>
425
+ <button onclick="copyKey('${key.key.replace(/'/g, "\\'")}')" class="text-gray-400 hover:text-white">
426
+ <i data-feather="copy" class="w-4 h-4"></i>
427
+ </button>
428
+ </div>
429
+ <div class="font-mono text-sm bg-gray-900 rounded p-2 mb-2 break-all">${key.key}</div>
430
+ <div class="text-xs text-gray-400">
431
+ <div class="flex items-center space-x-4">
432
+ <span><i data-feather="github" class="w-3 h-3 inline mr-1"></i>${key.repo}</span>
433
+ <span><i data-feather="file" class="w-3 h-3 inline mr-1"></i>${key.file.split('/').pop()}</span>
434
+ </div>
435
+ <div class="mt-1"><i data-feather="clock" class="w-3 h-3 inline mr-1"></i>${new Date(key.discoveredAt).toLocaleString()}</div>
436
+ </div>
437
+ </div>
438
+ `).join('');
439
+
440
+ feather.replace();
441
+ }
442
+
443
+ function filterKeys() {
444
+ updateKeysDisplay();
445
+ }
446
+
447
+ function copyKey(key) {
448
+ navigator.clipboard.writeText(key).then(() => {
449
+ Swal.fire({
450
+ icon: 'success',
451
+ title: 'Key Copied!',
452
+ text: 'API key copied to clipboard',
453
+ timer: 1500,
454
+ showConfirmButton: false
455
+ });
456
+ });
457
+ }
458
+
459
+ function exportKeys() {
460
+ const validOnly = validKeys.map(key => ({
461
+ key: key.key,
462
+ provider: key.provider,
463
+ repo: key.repo,
464
+ file: key.file,
465
+ discoveredAt: key.discoveredAt
466
+ }));
467
+
468
+ const dataStr = JSON.stringify(validOnly, null, 2);
469
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
470
+ const url = URL.createObjectURL(dataBlob);
471
+ const link = document.createElement('a');
472
+ link.href = url;
473
+ link.download = `api-keys-${new Date().toISOString().split('T')[0]}.json`;
474
+ link.click();
475
+ URL.revokeObjectURL(url);
476
+ }
477
+
478
+ function togglePause() {
479
+ isPaused = !isPaused;
480
+ pauseScanBtn.innerHTML = isPaused ?
481
+ '<i data-feather="play" class="w-4 h-4"></i>' :
482
+ '<i data-feather="pause" class="w-4 h-4"></i>';
483
+ feather.replace();
484
+ }
485
+ isScanning = false;
486
+ isPaused = false;
487
+
488
+ const scanStatus = document.getElementById('scanStatus');
489
+ scanStatus.textContent = 'Completed';
490
+ scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium status-active';
491
+
492
+ startScanBtn.innerHTML = '<i data-feather="play" class="w-4 h-4"></i><span>Start Scan</span>';
493
+ pauseScanBtn.classList.add('hidden');
494
+ feather.replace();
495
+
496
+ Swal.fire({
497
+ icon: 'success',
498
+ title: 'Scan Complete!',
499
+ text: `Scanned ${scannedRepos} repositories and found ${foundKeys.length} potential API keys (${validKeys.length} valid)`,
500
+ confirmButtonColor: '#667eea'
501
+ });
502
+ }
503
+
504
+ function stopScan() {
505
+ isScanning = false;
506
+ isPaused = false;
507
+
508
+ const scanStatus = document.getElementById('scanStatus');
509
+ scanStatus.textContent = 'Stopped';
510
+ scanStatus.className = 'px-3 py-1 rounded-full text-sm font-medium bg-gray-600';
511
+
512
+ startScanBtn.innerHTML = '<i data-feather="play" class="w-4 h-4"></i><span>Start Scan</span>';
513
+ pauseScanBtn.classList.add('hidden');
514
+ feather.replace();
515
+ }
516
+
517
+ function saveSettingsHandler() {
518
+ settings.githubToken = document.getElementById('githubToken').value;
519
+ settings.maxRepos = parseInt(document.getElementById('maxRepos').value);
520
+ settings.requestDelay = parseInt(document.getElementById('requestDelay').value);
521
+ settings.searchQuery = document.getElementById('searchQuery').value;
522
+
523
+ settingsModal.classList.add('hidden');
524
+
525
+ Swal.fire({
526
+ icon: 'success',
527
+ title: 'Settings Saved!',
528
+ text: 'Your scan settings have been updated',
529
+ timer: 1500,
530
+ showConfirmButton: false
531
+ });
532
+ }
533
+
534
+ function delay(ms) {
535
+ return new Promise(resolve => setTimeout(resolve, ms));
536
+ }
537
+
538
+ // Initialize
539
+ document.addEventListener('DOMContentLoaded', () => {
540
+ updateUI();
541
+ updateKeysDisplay();
542
+ });
543
+ </script>
544
+ </body>
545
  </html>