Rollbet Code Redeemer - Windows Style

Simple Windows 11 notification style code redeemer

// ==UserScript==
// @name         Rollbet Code Redeemer - Windows Style
// @namespace    http://tampermonkey.net/
// @version      6.0
// @description  Simple Windows 11 notification style code redeemer
// @author       You
// @match        https://rollbet.gg/*
// @match        https://*.rollbet.gg/*
// @grant        none
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    let isRunning = false;
    let codes = [];
    let stats = { processed: 0, startTime: null };
    let speedDelay = 500;
    let isMinimized = false;
    let currentMode = 'manual';

    function createControlPanel() {
        if (document.getElementById('rollbet-panel')) return;

        const panel = document.createElement('div');
        panel.id = 'rollbet-panel';
        
        // Inject Windows 11 style CSS
        const style = document.createElement('style');
        style.textContent = `
            #rollbet-panel {
                position: fixed;
                top: 20px;
                right: 20px;
                width: 300px;
                background: #2d2d30;
                border: 1px solid #3f3f46;
                border-radius: 8px;
                font-family: 'Segoe UI', system-ui, sans-serif;
                font-size: 14px;
                color: #ffffff;
                box-shadow: 0 8px 16px rgba(0,0,0,0.3);
                z-index: 999999;
                overflow: hidden;
            }
            
            .panel-header {
                background: #3c3c3c;
                padding: 12px 16px;
                border-bottom: 1px solid #3f3f46;
                display: flex;
                align-items: center;
                justify-content: space-between;
                cursor: move;
                user-select: none;
            }
            
            .panel-title {
                font-weight: 600;
                font-size: 15px;
                color: #ffffff;
            }
            
            .minimize-btn {
                width: 24px;
                height: 24px;
                border: none;
                border-radius: 4px;
                background: transparent;
                color: #ffffff;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 16px;
                transition: background 0.1s ease;
            }
            
            .minimize-btn:hover {
                background: #404040;
            }
            
            .panel-content {
                padding: 16px;
            }
            
            .mode-tabs {
                display: flex;
                margin-bottom: 16px;
                border-radius: 4px;
                overflow: hidden;
                border: 1px solid #3f3f46;
            }
            
            .mode-tab {
                flex: 1;
                padding: 8px 12px;
                border: none;
                background: #2d2d30;
                color: #cccccc;
                cursor: pointer;
                font-size: 13px;
                transition: all 0.1s ease;
            }
            
            .mode-tab:hover {
                background: #404040;
            }
            
            .mode-tab.active {
                background: #0078d4;
                color: #ffffff;
            }
            
            .section {
                margin-bottom: 16px;
            }
            
            .section:last-child {
                margin-bottom: 0;
            }
            
            .section-label {
                font-size: 13px;
                color: #cccccc;
                margin-bottom: 6px;
                display: block;
            }
            
            .input-field {
                width: 100%;
                padding: 8px 12px;
                background: #1e1e1e;
                border: 1px solid #3f3f46;
                border-radius: 4px;
                color: #ffffff;
                font-size: 13px;
                font-family: 'Consolas', 'Courier New', monospace;
                box-sizing: border-box;
                resize: vertical;
            }
            
            .input-field:focus {
                outline: none;
                border-color: #0078d4;
            }
            
            .input-field::placeholder {
                color: #6c6c6c;
            }
            
            .info-text {
                font-size: 12px;
                color: #999999;
                margin-top: 4px;
            }
            
            .slider-container {
                margin: 8px 0;
            }
            
            .speed-slider {
                width: 100%;
                height: 4px;
                border-radius: 2px;
                background: #3f3f46;
                outline: none;
                -webkit-appearance: none;
            }
            
            .speed-slider::-webkit-slider-thumb {
                -webkit-appearance: none;
                width: 16px;
                height: 16px;
                border-radius: 50%;
                background: #0078d4;
                cursor: pointer;
            }
            
            .speed-display {
                text-align: center;
                color: #0078d4;
                font-weight: 600;
                margin-top: 4px;
            }
            
            .button-row {
                display: flex;
                gap: 8px;
            }
            
            .btn {
                flex: 1;
                padding: 10px;
                border: none;
                border-radius: 4px;
                font-size: 13px;
                font-weight: 500;
                cursor: pointer;
                transition: background 0.1s ease;
            }
            
            .btn:disabled {
                opacity: 0.6;
                cursor: not-allowed;
            }
            
            .btn-primary {
                background: #0078d4;
                color: #ffffff;
            }
            
            .btn-primary:hover:not(:disabled) {
                background: #106ebe;
            }
            
            .btn-secondary {
                background: #d13438;
                color: #ffffff;
            }
            
            .btn-secondary:hover:not(:disabled) {
                background: #a4262c;
            }
            
            .btn-generate {
                width: 100%;
                padding: 10px;
                background: #0078d4;
                color: #ffffff;
                border: none;
                border-radius: 4px;
                font-size: 13px;
                font-weight: 500;
                cursor: pointer;
                margin-top: 12px;
                transition: background 0.1s ease;
            }
            
            .btn-generate:hover {
                background: #106ebe;
            }
            
            .stats-row {
                display: flex;
                justify-content: space-between;
                background: #1e1e1e;
                border: 1px solid #3f3f46;
                border-radius: 4px;
                padding: 12px;
            }
            
            .stat-item {
                text-align: center;
                flex: 1;
            }
            
            .stat-label {
                font-size: 11px;
                color: #999999;
                margin-bottom: 2px;
                text-transform: uppercase;
            }
            
            .stat-value {
                font-size: 14px;
                font-weight: 600;
                color: #ffffff;
            }
            
            .hidden { display: none !important; }
        `;
        document.head.appendChild(style);

        panel.innerHTML = `
            <div class="panel-header" id="drag-handle">
                <div class="panel-title">Code Redeemer</div>
                <button class="minimize-btn" id="minimize-btn">−</button>
            </div>
            
            <div class="panel-content" id="panel-content">
                <div class="mode-tabs">
                    <button class="mode-tab active" id="manual-tab">Manual</button>
                    <button class="mode-tab" id="generator-tab">Generator</button>
                </div>
                
                <div id="manual-section">
                    <div class="section">
                        <label class="section-label">Paste codes (one per line)</label>
                        <textarea class="input-field" id="codes-input" rows="4" 
                            placeholder="04SW-A6KS-B70S&#10;04AW-A6KS-BO0S&#10;04PW-A6KS-BH0S"></textarea>
                        <div class="info-text" id="code-count">0 codes loaded</div>
                    </div>
                </div>
                
                <div id="generator-section" class="hidden">
                    <div class="section">
                        <label class="section-label">Code pattern (* for unknown)</label>
                        <input class="input-field" id="pattern-input" type="text"
                            placeholder="04*W-A6KS-B*0S" style="text-align: center;">
                        <div class="info-text" id="pattern-info">Use * for missing characters</div>
                        <button class="btn-generate" id="generate-btn">Generate All Codes</button>
                    </div>
                </div>
                
                <div class="section">
                    <label class="section-label">Speed: <span id="speed-display">2.0 codes/sec</span></label>
                    <div class="slider-container">
                        <input type="range" class="speed-slider" id="speed-slider" 
                            min="100" max="3000" value="500">
                    </div>
                </div>
                
                <div class="section">
                    <div class="button-row">
                        <button class="btn btn-primary" id="start-btn">Start</button>
                        <button class="btn btn-secondary" id="stop-btn" disabled>Stop</button>
                    </div>
                </div>
                
                <div class="section">
                    <div class="stats-row">
                        <div class="stat-item">
                            <div class="stat-label">Status</div>
                            <div class="stat-value" id="status-text">Ready</div>
                        </div>
                        <div class="stat-item">
                            <div class="stat-label">Progress</div>
                            <div class="stat-value" id="progress-text">0 / 0</div>
                        </div>
                        <div class="stat-item">
                            <div class="stat-label">Speed</div>
                            <div class="stat-value" id="speed-text">0/sec</div>
                        </div>
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(panel);

        // Event listeners
        setupEventListeners();
        makeDraggable();
    }

    function setupEventListeners() {
        document.getElementById('minimize-btn').onclick = toggleMinimize;
        document.getElementById('manual-tab').onclick = () => switchMode('manual');
        document.getElementById('generator-tab').onclick = () => switchMode('generator');
        document.getElementById('codes-input').oninput = updateCodeCount;
        document.getElementById('pattern-input').oninput = updatePatternInfo;
        document.getElementById('generate-btn').onclick = generateCodes;
        document.getElementById('speed-slider').oninput = updateSpeedDisplay;
        document.getElementById('start-btn').onclick = startRedemption;
        document.getElementById('stop-btn').onclick = stopRedemption;
    }

    function makeDraggable() {
        const panel = document.getElementById('rollbet-panel');
        const handle = document.getElementById('drag-handle');
        let isDragging = false;
        let startX, startY, initialX, initialY;

        handle.addEventListener('mousedown', (e) => {
            if (e.target.id === 'minimize-btn') return;
            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            initialX = panel.offsetLeft;
            initialY = panel.offsetTop;
            e.preventDefault();
            document.addEventListener('mousemove', handleDrag);
            document.addEventListener('mouseup', stopDrag);
        });

        function handleDrag(e) {
            if (!isDragging) return;
            e.preventDefault();
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            const newX = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, initialX + dx));
            const newY = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, initialY + dy));
            panel.style.left = newX + 'px';
            panel.style.top = newY + 'px';
        }

        function stopDrag() {
            isDragging = false;
            document.removeEventListener('mousemove', handleDrag);
            document.removeEventListener('mouseup', stopDrag);
        }
    }

    function toggleMinimize() {
        const content = document.getElementById('panel-content');
        const btn = document.getElementById('minimize-btn');
        const panel = document.getElementById('rollbet-panel');
        
        if (isMinimized) {
            content.classList.remove('hidden');
            btn.textContent = '−';
            panel.style.width = '300px';
            isMinimized = false;
        } else {
            content.classList.add('hidden');
            btn.textContent = '+';
            panel.style.width = 'auto';
            isMinimized = true;
        }
    }

    function switchMode(mode) {
        const manualTab = document.getElementById('manual-tab');
        const generatorTab = document.getElementById('generator-tab');
        const manualSection = document.getElementById('manual-section');
        const generatorSection = document.getElementById('generator-section');
        
        currentMode = mode;
        
        if (mode === 'manual') {
            manualTab.classList.add('active');
            generatorTab.classList.remove('active');
            manualSection.classList.remove('hidden');
            generatorSection.classList.add('hidden');
        } else {
            generatorTab.classList.add('active');
            manualTab.classList.remove('active');
            generatorSection.classList.remove('hidden');
            manualSection.classList.add('hidden');
            updatePatternInfo();
        }
    }

    function updateCodeCount() {
        const text = document.getElementById('codes-input').value.trim();
        const count = text ? text.split('\n').filter(line => line.trim().length > 0).length : 0;
        document.getElementById('code-count').textContent = `${count.toLocaleString()} codes loaded`;
    }

    function updatePatternInfo() {
        const pattern = document.getElementById('pattern-input').value.trim();
        const wildcards = (pattern.match(/\*/g) || []).length;
        const total = Math.pow(36, wildcards);
        
        if (wildcards > 0) {
            document.getElementById('pattern-info').textContent = 
                `${wildcards} wildcards = ${total.toLocaleString()} combinations`;
        } else {
            document.getElementById('pattern-info').textContent = 'Use * for missing characters';
        }
    }

    function updateSpeedDisplay() {
        const slider = document.getElementById('speed-slider');
        speedDelay = parseInt(slider.value);
        const speed = (1000 / speedDelay).toFixed(1);
        document.getElementById('speed-display').textContent = `${speed} codes/sec`;
    }

    function generateCodes() {
        const pattern = document.getElementById('pattern-input').value.trim().toUpperCase();
        if (!pattern) {
            alert('Enter a pattern first!');
            return;
        }

        const wildcards = (pattern.match(/\*/g) || []).length;
        if (wildcards === 0) {
            alert('No wildcards (*) found in pattern!');
            return;
        }

        if (wildcards > 3) {
            if (!confirm(`This will generate ${Math.pow(36, wildcards).toLocaleString()} codes. Continue?`)) return;
        }

        const btn = document.getElementById('generate-btn');
        btn.textContent = 'Generating...';
        btn.disabled = true;

        setTimeout(() => {
            const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
            
            function generate(pattern) {
                if (!pattern.includes('*')) return [pattern];
                const results = [];
                for (let char of chars) {
                    results.push(...generate(pattern.replace('*', char)));
                }
                return results;
            }

            const codes = generate(pattern);
            
            // Shuffle randomly
            for (let i = codes.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [codes[i], codes[j]] = [codes[j], codes[i]];
            }

            document.getElementById('codes-input').value = codes.join('\n');
            updateCodeCount();
            switchMode('manual');
            
            btn.textContent = 'Generate All Codes';
            btn.disabled = false;
            
            alert(`Generated ${codes.length.toLocaleString()} codes successfully!`);
        }, 100);
    }

    function findInput() {
        return document.querySelector('input[placeholder="xxxx-xxxx-xxxx-xxxx"]');
    }

    function findRedeemButton() {
        const buttons = document.querySelectorAll('button[type="submit"]');
        for (let btn of buttons) {
            if (btn.textContent.includes('Redeem Gift Card')) return btn;
        }
        return null;
    }

    function updateStats() {
        if (!stats.startTime) return;
        const elapsed = (Date.now() - stats.startTime) / 1000;
        const speed = elapsed > 0 ? stats.processed / elapsed : 0;
        document.getElementById('progress-text').textContent = `${stats.processed} / ${codes.length}`;
        document.getElementById('speed-text').textContent = `${speed.toFixed(1)}/sec`;
    }

    async function typeAndRedeem(code, input, button) {
        input.value = code;
        input.dispatchEvent(new Event('input', { bubbles: true }));
        await new Promise(r => setTimeout(r, 20));
        button.click();
    }

    async function startRedemption() {
        if (isRunning) return;

        const text = document.getElementById('codes-input').value.trim();
        if (!text) {
            alert('Please enter some codes first!');
            return;
        }

        codes = text.split('\n').filter(line => line.trim().length > 0);
        const input = findInput();
        const button = findRedeemButton();

        if (!input || !button) {
            alert('Could not find input field or redeem button on this page!');
            return;
        }

        isRunning = true;
        stats = { processed: 0, startTime: Date.now() };

        document.getElementById('start-btn').disabled = true;
        document.getElementById('stop-btn').disabled = false;
        document.getElementById('status-text').textContent = 'Running';

        for (let i = 0; i < codes.length && isRunning; i++) {
            try {
                await typeAndRedeem(codes[i], input, button);
                stats.processed++;
                updateStats();
                if (i < codes.length - 1 && isRunning) {
                    await new Promise(r => setTimeout(r, speedDelay));
                }
            } catch (e) {
                stats.processed++;
                updateStats();
            }
        }

        if (isRunning) {
            document.getElementById('status-text').textContent = 'Complete';
        }
        stopRedemption();
    }

    function stopRedemption() {
        isRunning = false;
        document.getElementById('start-btn').disabled = false;
        document.getElementById('stop-btn').disabled = true;
        if (stats.processed === 0) {
            document.getElementById('status-text').textContent = 'Ready';
        }
    }

    function init() {
        if (!window.location.hostname.includes('rollbet.gg')) return;
        setTimeout(createControlPanel, 1000);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();