SatoshiFaucet Claim Organizer & Timer

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})();
SatoshiFaucet Claim Organizer & Timer | Robomonkey