Track faucet claims, manage timers, view statistics, and organize your crypto faucet earnings efficiently
Size
24.1 KB
Version
1.0.1
Created
Nov 5, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name SatoshiFaucet Claim Organizer & Timer
3// @description Track faucet claims, manage timers, view statistics, and organize your crypto faucet earnings efficiently
4// @version 1.0.1
5// @match https://*.satoshifaucet.io/*
6// ==/UserScript==
7(function() {
8 'use strict';
9
10 // Utility function for debouncing
11 function debounce(func, wait) {
12 let timeout;
13 return function executedFunction(...args) {
14 const later = () => {
15 clearTimeout(timeout);
16 func(...args);
17 };
18 clearTimeout(timeout);
19 timeout = setTimeout(later, wait);
20 };
21 }
22
23 // Data management functions
24 async function getClaimHistory() {
25 const history = await GM.getValue('claim_history', '[]');
26 return JSON.parse(history);
27 }
28
29 async function saveClaimHistory(history) {
30 await GM.setValue('claim_history', JSON.stringify(history));
31 }
32
33 async function addClaim(currency, amount) {
34 const history = await getClaimHistory();
35 const claim = {
36 currency: currency,
37 amount: amount,
38 timestamp: Date.now(),
39 date: new Date().toLocaleString()
40 };
41 history.unshift(claim);
42 // Keep only last 100 claims
43 if (history.length > 100) {
44 history.pop();
45 }
46 await saveClaimHistory(history);
47 console.log('Claim recorded:', claim);
48 return claim;
49 }
50
51 async function getStatistics() {
52 const history = await getClaimHistory();
53 const stats = {
54 totalClaims: history.length,
55 currencies: {},
56 today: 0,
57 thisWeek: 0,
58 thisMonth: 0
59 };
60
61 const now = Date.now();
62 const oneDayAgo = now - (24 * 60 * 60 * 1000);
63 const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000);
64 const oneMonthAgo = now - (30 * 24 * 60 * 60 * 1000);
65
66 history.forEach(claim => {
67 // Count by currency
68 if (!stats.currencies[claim.currency]) {
69 stats.currencies[claim.currency] = {
70 count: 0,
71 total: 0
72 };
73 }
74 stats.currencies[claim.currency].count++;
75 stats.currencies[claim.currency].total += parseFloat(claim.amount) || 0;
76
77 // Count by time period
78 if (claim.timestamp > oneDayAgo) stats.today++;
79 if (claim.timestamp > oneWeekAgo) stats.thisWeek++;
80 if (claim.timestamp > oneMonthAgo) stats.thisMonth++;
81 });
82
83 return stats;
84 }
85
86 async function getTimers() {
87 const timers = await GM.getValue('faucet_timers', '{}');
88 return JSON.parse(timers);
89 }
90
91 async function saveTimers(timers) {
92 await GM.setValue('faucet_timers', JSON.stringify(timers));
93 }
94
95 async function setTimer(currency, seconds) {
96 const timers = await getTimers();
97 timers[currency] = {
98 endTime: Date.now() + (seconds * 1000),
99 duration: seconds
100 };
101 await saveTimers(timers);
102 console.log(`Timer set for ${currency}: ${seconds} seconds`);
103 }
104
105 // UI Creation
106 function createOrganizerPanel() {
107 const panel = document.createElement('div');
108 panel.id = 'faucet-organizer-panel';
109 panel.style.cssText = `
110 position: fixed;
111 top: 80px;
112 right: 20px;
113 width: 350px;
114 max-height: 600px;
115 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
116 border-radius: 15px;
117 box-shadow: 0 10px 40px rgba(0,0,0,0.3);
118 z-index: 999999;
119 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
120 overflow: hidden;
121 transition: all 0.3s ease;
122 `;
123
124 panel.innerHTML = `
125 <div style="background: rgba(255,255,255,0.95); border-radius: 15px; margin: 2px; height: 100%;">
126 <div style="padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: flex; justify-content: space-between; align-items: center; border-radius: 13px 13px 0 0;">
127 <div style="display: flex; align-items: center; gap: 10px;">
128 <i class="fa-solid fa-faucet" style="font-size: 20px;"></i>
129 <h3 style="margin: 0; font-size: 16px; font-weight: 600;">Faucet Organizer</h3>
130 </div>
131 <div style="display: flex; gap: 10px;">
132 <button id="organizer-minimize" style="background: rgba(255,255,255,0.2); border: none; color: white; width: 28px; height: 28px; border-radius: 6px; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center;">−</button>
133 <button id="organizer-close" style="background: rgba(255,255,255,0.2); border: none; color: white; width: 28px; height: 28px; border-radius: 6px; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center;">×</button>
134 </div>
135 </div>
136
137 <div id="organizer-content" style="padding: 15px; max-height: 520px; overflow-y: auto;">
138 <!-- Tabs -->
139 <div style="display: flex; gap: 5px; margin-bottom: 15px; background: #f0f0f0; padding: 4px; border-radius: 8px;">
140 <button class="organizer-tab active" data-tab="timers" style="flex: 1; padding: 8px; border: none; background: white; color: #667eea; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 13px; transition: all 0.2s;">Timers</button>
141 <button class="organizer-tab" data-tab="history" style="flex: 1; padding: 8px; border: none; background: transparent; color: #666; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 13px; transition: all 0.2s;">History</button>
142 <button class="organizer-tab" data-tab="stats" style="flex: 1; padding: 8px; border: none; background: transparent; color: #666; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 13px; transition: all 0.2s;">Stats</button>
143 </div>
144
145 <!-- Timers Tab -->
146 <div id="tab-timers" class="organizer-tab-content">
147 <div id="timers-list" style="display: flex; flex-direction: column; gap: 10px;">
148 <div style="text-align: center; padding: 30px; color: #999;">
149 <i class="fa-solid fa-clock" style="font-size: 40px; margin-bottom: 10px; opacity: 0.3;"></i>
150 <p style="margin: 0;">No active timers</p>
151 </div>
152 </div>
153 </div>
154
155 <!-- History Tab -->
156 <div id="tab-history" class="organizer-tab-content" style="display: none;">
157 <div id="history-list" style="display: flex; flex-direction: column; gap: 8px;">
158 <div style="text-align: center; padding: 30px; color: #999;">
159 <i class="fa-solid fa-history" style="font-size: 40px; margin-bottom: 10px; opacity: 0.3;"></i>
160 <p style="margin: 0;">No claim history</p>
161 </div>
162 </div>
163 </div>
164
165 <!-- Stats Tab -->
166 <div id="tab-stats" class="organizer-tab-content" style="display: none;">
167 <div id="stats-content">
168 <div style="text-align: center; padding: 30px; color: #999;">
169 <i class="fa-solid fa-chart-bar" style="font-size: 40px; margin-bottom: 10px; opacity: 0.3;"></i>
170 <p style="margin: 0;">No statistics available</p>
171 </div>
172 </div>
173 </div>
174 </div>
175 </div>
176 `;
177
178 document.body.appendChild(panel);
179 console.log('Organizer panel created');
180
181 // Setup event listeners
182 setupPanelEvents(panel);
183
184 return panel;
185 }
186
187 function setupPanelEvents(panel) {
188 // Close button
189 const closeBtn = panel.querySelector('#organizer-close');
190 closeBtn.addEventListener('click', () => {
191 panel.style.display = 'none';
192 console.log('Organizer panel closed');
193 });
194
195 // Minimize button
196 const minimizeBtn = panel.querySelector('#organizer-minimize');
197 const content = panel.querySelector('#organizer-content');
198 let isMinimized = false;
199
200 minimizeBtn.addEventListener('click', () => {
201 isMinimized = !isMinimized;
202 if (isMinimized) {
203 content.style.display = 'none';
204 panel.style.maxHeight = 'auto';
205 minimizeBtn.textContent = '+';
206 } else {
207 content.style.display = 'block';
208 panel.style.maxHeight = '600px';
209 minimizeBtn.textContent = '−';
210 }
211 });
212
213 // Tab switching
214 const tabs = panel.querySelectorAll('.organizer-tab');
215 tabs.forEach(tab => {
216 tab.addEventListener('click', () => {
217 const tabName = tab.dataset.tab;
218
219 // Update tab buttons
220 tabs.forEach(t => {
221 t.style.background = 'transparent';
222 t.style.color = '#666';
223 t.classList.remove('active');
224 });
225 tab.style.background = 'white';
226 tab.style.color = '#667eea';
227 tab.classList.add('active');
228
229 // Update tab content
230 panel.querySelectorAll('.organizer-tab-content').forEach(content => {
231 content.style.display = 'none';
232 });
233 panel.querySelector(`#tab-${tabName}`).style.display = 'block';
234
235 // Refresh content
236 if (tabName === 'timers') updateTimersDisplay();
237 if (tabName === 'history') updateHistoryDisplay();
238 if (tabName === 'stats') updateStatsDisplay();
239 });
240 });
241 }
242
243 async function updateTimersDisplay() {
244 const timersList = document.querySelector('#timers-list');
245 if (!timersList) return;
246
247 const timers = await getTimers();
248 const now = Date.now();
249 const activeTimers = [];
250
251 for (const [currency, timer] of Object.entries(timers)) {
252 if (timer.endTime > now) {
253 activeTimers.push({ currency, ...timer });
254 }
255 }
256
257 if (activeTimers.length === 0) {
258 timersList.innerHTML = `
259 <div style="text-align: center; padding: 30px; color: #999;">
260 <i class="fa-solid fa-clock" style="font-size: 40px; margin-bottom: 10px; opacity: 0.3;"></i>
261 <p style="margin: 0;">No active timers</p>
262 <p style="margin: 5px 0 0 0; font-size: 12px;">Timers will appear after claims</p>
263 </div>
264 `;
265 return;
266 }
267
268 timersList.innerHTML = activeTimers.map(timer => {
269 const remaining = Math.max(0, timer.endTime - now);
270 const seconds = Math.floor(remaining / 1000);
271 const minutes = Math.floor(seconds / 60);
272 const hours = Math.floor(minutes / 60);
273
274 let timeDisplay;
275 if (hours > 0) {
276 timeDisplay = `${hours}h ${minutes % 60}m`;
277 } else if (minutes > 0) {
278 timeDisplay = `${minutes}m ${seconds % 60}s`;
279 } else {
280 timeDisplay = `${seconds}s`;
281 }
282
283 const progress = ((timer.duration * 1000 - remaining) / (timer.duration * 1000)) * 100;
284
285 return `
286 <div style="background: white; padding: 12px; border-radius: 10px; border: 2px solid #f0f0f0;">
287 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
288 <span style="font-weight: 600; color: #333; text-transform: uppercase; font-size: 14px;">${timer.currency}</span>
289 <span style="font-weight: 700; color: ${remaining < 60000 ? '#f44336' : '#667eea'}; font-size: 14px;">${timeDisplay}</span>
290 </div>
291 <div style="background: #f0f0f0; height: 6px; border-radius: 3px; overflow: hidden;">
292 <div style="background: linear-gradient(90deg, #667eea, #764ba2); height: 100%; width: ${progress}%; transition: width 1s linear;"></div>
293 </div>
294 </div>
295 `;
296 }).join('');
297 }
298
299 async function updateHistoryDisplay() {
300 const historyList = document.querySelector('#history-list');
301 if (!historyList) return;
302
303 const history = await getClaimHistory();
304
305 if (history.length === 0) {
306 historyList.innerHTML = `
307 <div style="text-align: center; padding: 30px; color: #999;">
308 <i class="fa-solid fa-history" style="font-size: 40px; margin-bottom: 10px; opacity: 0.3;"></i>
309 <p style="margin: 0;">No claim history</p>
310 <p style="margin: 5px 0 0 0; font-size: 12px;">Your claims will appear here</p>
311 </div>
312 `;
313 return;
314 }
315
316 historyList.innerHTML = history.slice(0, 20).map(claim => `
317 <div style="background: white; padding: 10px 12px; border-radius: 8px; border-left: 4px solid #667eea;">
318 <div style="display: flex; justify-content: space-between; align-items: center;">
319 <div>
320 <div style="font-weight: 600; color: #333; font-size: 13px; text-transform: uppercase;">${claim.currency}</div>
321 <div style="font-size: 11px; color: #999; margin-top: 2px;">${claim.date}</div>
322 </div>
323 <div style="font-weight: 700; color: #667eea; font-size: 13px;">${claim.amount}</div>
324 </div>
325 </div>
326 `).join('');
327 }
328
329 async function updateStatsDisplay() {
330 const statsContent = document.querySelector('#stats-content');
331 if (!statsContent) return;
332
333 const stats = await getStatistics();
334
335 if (stats.totalClaims === 0) {
336 statsContent.innerHTML = `
337 <div style="text-align: center; padding: 30px; color: #999;">
338 <i class="fa-solid fa-chart-bar" style="font-size: 40px; margin-bottom: 10px; opacity: 0.3;"></i>
339 <p style="margin: 0;">No statistics available</p>
340 <p style="margin: 5px 0 0 0; font-size: 12px;">Start claiming to see stats</p>
341 </div>
342 `;
343 return;
344 }
345
346 const currencyStats = Object.entries(stats.currencies)
347 .sort((a, b) => b[1].count - a[1].count)
348 .map(([currency, data]) => `
349 <div style="background: white; padding: 10px 12px; border-radius: 8px; margin-bottom: 8px;">
350 <div style="display: flex; justify-content: space-between; align-items: center;">
351 <span style="font-weight: 600; color: #333; text-transform: uppercase; font-size: 13px;">${currency}</span>
352 <span style="font-weight: 700; color: #667eea; font-size: 13px;">${data.count} claims</span>
353 </div>
354 <div style="font-size: 11px; color: #999; margin-top: 4px;">Total: ${data.total.toFixed(8)}</div>
355 </div>
356 `).join('');
357
358 statsContent.innerHTML = `
359 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 15px;">
360 <div style="background: linear-gradient(135deg, #667eea, #764ba2); padding: 12px; border-radius: 10px; text-align: center; color: white;">
361 <div style="font-size: 20px; font-weight: 700;">${stats.today}</div>
362 <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">Today</div>
363 </div>
364 <div style="background: linear-gradient(135deg, #f093fb, #f5576c); padding: 12px; border-radius: 10px; text-align: center; color: white;">
365 <div style="font-size: 20px; font-weight: 700;">${stats.thisWeek}</div>
366 <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">This Week</div>
367 </div>
368 <div style="background: linear-gradient(135deg, #4facfe, #00f2fe); padding: 12px; border-radius: 10px; text-align: center; color: white;">
369 <div style="font-size: 20px; font-weight: 700;">${stats.thisMonth}</div>
370 <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">This Month</div>
371 </div>
372 </div>
373
374 <div style="background: #f8f9fa; padding: 12px; border-radius: 10px; margin-bottom: 15px; text-align: center;">
375 <div style="font-size: 24px; font-weight: 700; color: #667eea;">${stats.totalClaims}</div>
376 <div style="font-size: 12px; color: #666; margin-top: 2px;">Total Claims</div>
377 </div>
378
379 <div style="font-weight: 600; color: #333; margin-bottom: 10px; font-size: 13px;">By Currency</div>
380 ${currencyStats}
381 `;
382 }
383
384 // Create toggle button
385 function createToggleButton() {
386 const button = document.createElement('button');
387 button.id = 'organizer-toggle-btn';
388 button.innerHTML = '<i class="fa-solid fa-faucet"></i>';
389 button.style.cssText = `
390 position: fixed;
391 bottom: 30px;
392 right: 30px;
393 width: 60px;
394 height: 60px;
395 border-radius: 50%;
396 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
397 border: none;
398 color: white;
399 font-size: 24px;
400 cursor: pointer;
401 box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
402 z-index: 999998;
403 transition: all 0.3s ease;
404 display: flex;
405 align-items: center;
406 justify-content: center;
407 `;
408
409 button.addEventListener('mouseenter', () => {
410 button.style.transform = 'scale(1.1)';
411 button.style.boxShadow = '0 6px 30px rgba(102, 126, 234, 0.6)';
412 });
413
414 button.addEventListener('mouseleave', () => {
415 button.style.transform = 'scale(1)';
416 button.style.boxShadow = '0 4px 20px rgba(102, 126, 234, 0.4)';
417 });
418
419 button.addEventListener('click', () => {
420 const panel = document.querySelector('#faucet-organizer-panel');
421 if (panel) {
422 panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
423 if (panel.style.display === 'block') {
424 updateTimersDisplay();
425 }
426 }
427 });
428
429 document.body.appendChild(button);
430 console.log('Toggle button created');
431 }
432
433 // Monitor for successful claims
434 function monitorClaims() {
435 console.log('Starting claim monitoring...');
436
437 // Watch for success messages
438 const observer = new MutationObserver(debounce(async (mutations) => {
439 for (const mutation of mutations) {
440 for (const node of mutation.addedNodes) {
441 if (node.nodeType === 1) {
442 // Check for success alerts
443 const successAlert = node.querySelector?.('.alert-success') ||
444 (node.classList?.contains('alert-success') ? node : null);
445
446 if (successAlert) {
447 const text = successAlert.textContent;
448 console.log('Success alert detected:', text);
449
450 // Extract currency and amount
451 const currencyMatch = text.match(/(\d+\.?\d*)\s*([A-Z]{3,})/i);
452 if (currencyMatch) {
453 const amount = currencyMatch[1];
454 const currency = currencyMatch[2].toUpperCase();
455
456 await addClaim(currency, amount);
457 await setTimer(currency, 10); // 10 seconds default timer
458
459 // Show notification
460 showNotification(`Claimed ${amount} ${currency}!`, 'success');
461
462 // Update displays
463 updateTimersDisplay();
464 updateHistoryDisplay();
465 updateStatsDisplay();
466 }
467 }
468 }
469 }
470 }
471 }, 500));
472
473 observer.observe(document.body, {
474 childList: true,
475 subtree: true
476 });
477
478 console.log('Claim monitoring active');
479 }
480
481 // Show notification
482 function showNotification(message, type = 'info') {
483 const notification = document.createElement('div');
484 notification.style.cssText = `
485 position: fixed;
486 top: 20px;
487 right: 20px;
488 background: ${type === 'success' ? 'linear-gradient(135deg, #11998e, #38ef7d)' : 'linear-gradient(135deg, #667eea, #764ba2)'};
489 color: white;
490 padding: 15px 20px;
491 border-radius: 10px;
492 box-shadow: 0 4px 20px rgba(0,0,0,0.2);
493 z-index: 9999999;
494 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
495 font-weight: 600;
496 animation: slideIn 0.3s ease;
497 `;
498 notification.textContent = message;
499
500 document.body.appendChild(notification);
501
502 setTimeout(() => {
503 notification.style.animation = 'slideOut 0.3s ease';
504 setTimeout(() => notification.remove(), 300);
505 }, 3000);
506 }
507
508 // Add CSS animations
509 TM_addStyle(`
510 @keyframes slideIn {
511 from {
512 transform: translateX(400px);
513 opacity: 0;
514 }
515 to {
516 transform: translateX(0);
517 opacity: 1;
518 }
519 }
520
521 @keyframes slideOut {
522 from {
523 transform: translateX(0);
524 opacity: 1;
525 }
526 to {
527 transform: translateX(400px);
528 opacity: 0;
529 }
530 }
531
532 #organizer-content::-webkit-scrollbar {
533 width: 6px;
534 }
535
536 #organizer-content::-webkit-scrollbar-track {
537 background: #f1f1f1;
538 border-radius: 10px;
539 }
540
541 #organizer-content::-webkit-scrollbar-thumb {
542 background: #667eea;
543 border-radius: 10px;
544 }
545
546 #organizer-content::-webkit-scrollbar-thumb:hover {
547 background: #764ba2;
548 }
549 `);
550
551 // Update timers every second
552 function startTimerUpdates() {
553 setInterval(() => {
554 const activeTab = document.querySelector('.organizer-tab.active');
555 if (activeTab && activeTab.dataset.tab === 'timers') {
556 updateTimersDisplay();
557 }
558 }, 1000);
559 }
560
561 // Initialize
562 async function init() {
563 console.log('Faucet Claim Organizer initializing...');
564
565 // Wait for page to be ready
566 if (document.readyState === 'loading') {
567 document.addEventListener('DOMContentLoaded', init);
568 return;
569 }
570
571 // Create UI
572 setTimeout(() => {
573 createToggleButton();
574 createOrganizerPanel();
575 monitorClaims();
576 startTimerUpdates();
577
578 // Initial display update
579 updateTimersDisplay();
580
581 console.log('Faucet Claim Organizer ready!');
582 }, 1000);
583 }
584
585 // Start the extension
586 init();
587})();