Duolingo Account Creator

Create Duolingo accounts

// ==UserScript==
// @name         Duolingo Account Creator
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Create Duolingo accounts 
// @author       You
// @match        https://www.duolingo.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_getValue
// @grant        GM_setValue
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @connect      duolingo.com
// @connect      www.duolingo.com
// @connect      https://www.duolingo.com/2017-06-30/user
// @discord      https://discord.gg/kfct8FuHmW
// @connect      api.ipify.org
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const MAX_ACCOUNTS = 5000;

    const style = document.createElement('style');
    style.textContent = `
        :root {
            --bg-color: #2B2D31;
            --panel-color: #3E4043;
            --text-color: #E6E6E6;
            --border-color: #4D4F53;
            --primary-color: #58CC02;
            --primary-hover: #61E002;
            --primary-shadow: #46A302;
            --info-color: #1CB0F6;
            --warning-color: #FFC107;
            --error-color: #FF6B6B;
            --secondary-text: #B5B5B5;
            --button-bg: #3E4043;
            --button-text: #E6E6E6;
            --button-hover-text: #FFFFFF;
            --input-bg: #3E4043;
        }

        :root.light-mode {
            --bg-color: #FFFFFF;
            --panel-color: #F5F5F5;
            --text-color: #333333;
            --border-color: #E0E0E0;
            --primary-color: #58CC02;
            --primary-hover: #61E002;
            --primary-shadow: #46A302;
            --info-color: #1CB0F6;
            --warning-color: #FFC107;
            --error-color: #FF6B6B;
            --secondary-text: #666666;
            --button-bg: #E0E0E0;
            --button-text: #333333;
            --button-hover-text: #FFFFFF;
            --input-bg: #F5F5F5;
        }

        #duolingo-account-creator * {
            box-sizing: border-box;
        }

        .account-row:hover .delete-account-btn {
            display: inline-block;
        }

        .delete-account-btn {
            display: none;
            background-color: var(--error-color) !important;
            color: white !important;
            border: none;
            border-radius: 4px;
            padding: 2px 6px;
            font-size: 11px;
            cursor: pointer;
            margin-left: 6px;
        }

        .delete-account-btn:hover {
            background-color: #ff5252 !important;
        }
    `;
    document.head.appendChild(style);

    const state = {
        accounts: [],
        isCreating: false,
        notifications: [],
        activeMenu: null,
        userIP: null,
        settings: JSON.parse(localStorage.getItem('duolingoAccountCreatorSettings')) || {
            saveAccounts: true,
            randomEmail: true,
            randomPassword: true,
            creationSpeed: 'medium',
            theme: 'dark'
        },
        createdCount: 0,
        totalCount: 0
    };

    const elements = {
        mainContainer: null,
        settingsPanel: null,
        logsPanel: null,
        notificationsPanel: null,
        accountProgress: null
    };

    const utils = {
        async getUserIP() {
            try {
                const response = await fetch('https://api.ipify.org?format=json');
                const data = await response.json();
                return data.ip;
            } catch (error) {
                console.error('Error getting IP:', error);
                return 'unknown';
            }
        },

        applyTheme() {
            if (state.settings.theme === 'light') {
                document.documentElement.classList.add('light-mode');
            } else {
                document.documentElement.classList.remove('light-mode');
            }
            this.updateAllUI();
        },

        updateAllUI() {
            if (elements.mainContainer) {
                elements.mainContainer.style.backgroundColor = 'var(--bg-color)';
                elements.mainContainer.style.borderColor = 'var(--border-color)';
                elements.mainContainer.style.color = 'var(--text-color)';
            }

            document.querySelectorAll('#duolingo-account-creator button, #duolingo-account-creator input, #duolingo-account-creator select').forEach(el => {
                if (el.tagName === 'BUTTON' && !el.id.includes('start-create')) {
                    el.style.color = 'var(--button-text)';
                    el.style.backgroundColor = 'var(--button-bg)';
                }
            });
        },

        closeAllMenus() {
            for (const panel in elements) {
                if (panel !== 'mainContainer' && elements[panel] && panel !== 'accountProgress') {
                    elements[panel].style.display = 'none';
                }
            }
            state.activeMenu = null;
        },

        createIconButton(icon, normalColor, hoverColor, titleText = '') {
            const btn = document.createElement('button');
            btn.innerHTML = icon;
            btn.title = titleText;
            Object.assign(btn.style, {
                width: '28px',
                height: '28px',
                borderRadius: '50%',
                border: 'none',
                backgroundColor: normalColor === 'transparent' ? 'transparent' : 'var(--button-bg)',
                color: 'var(--button-text)',
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                cursor: 'pointer',
                transition: 'all 0.2s',
                fontSize: '14px',
                margin: '0 2px'
            });

            btn.addEventListener('mouseover', () => {
                btn.style.backgroundColor = hoverColor;
                btn.style.color = 'var(--button-hover-text)';
            });

            btn.addEventListener('mouseout', () => {
                btn.style.backgroundColor = normalColor === 'transparent' ? 'transparent' : 'var(--button-bg)';
                btn.style.color = 'var(--button-text)';
            });

            return btn;
        },

        makeDraggable(element, handle) {
            let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

            handle.onmousedown = dragMouseDown;

            function dragMouseDown(e) {
                e = e || window.event;
                e.preventDefault();
                pos3 = e.clientX;
                pos4 = e.clientY;
                document.onmouseup = closeDragElement;
                document.onmousemove = elementDrag;
            }

            function elementDrag(e) {
                e = e || window.event;
                e.preventDefault();
                pos1 = pos3 - e.clientX;
                pos2 = pos4 - e.clientY;
                pos3 = e.clientX;
                pos4 = e.clientY;
                element.style.top = (element.offsetTop - pos2) + "px";
                element.style.left = (element.offsetLeft - pos1) + "px";
            }

            function closeDragElement() {
                document.onmouseup = null;
                document.onmousemove = null;
            }
        },

        generateRandomEmail() {
            const domainSelect = document.getElementById('email-domain');
            const domain = domainSelect.value === 'custom'
                ? document.getElementById('custom-domain').value
                : domainSelect.value;

            const randomString = Math.random().toString(36).substring(2, 10);
            const randomNumber = Math.floor(Math.random() * 1000);
            return `${randomString}${randomNumber}@${domain}`;
        },

        generateRandomPassword() {
            const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()';
            let password = '';
            for (let i = 0; i < 12; i++) {
                password += chars.charAt(Math.floor(Math.random() * chars.length));
            }
            return password;
        },

        generateRandomUsername() {
            const randomString = Math.random().toString(36).substring(2, 10);
            const randomNumber = Math.floor(Math.random() * 1000);
            return `user_${randomString}${randomNumber}`;
        },

        saveSettings() {
            localStorage.setItem('duolingoAccountCreatorSettings', JSON.stringify(state.settings));
            this.applyTheme();
        },

        addNotification(message, type = 'info') {
            const notification = {
                id: Date.now(),
                message,
                type,
                timestamp: new Date().toLocaleTimeString()
            };
            state.notifications.unshift(notification);
            if (elements.notificationsPanel) {
                ui.updateNotificationsPanel();
            }
        },

        saveAccountsToStorage() {
            if (state.settings.saveAccounts) {
                const accountsToSave = state.accounts.map(account => ({
                    ...account,
                    ip: state.userIP,
                    createdAt: new Date().toISOString()
                }));
                localStorage.setItem('duolingoAccounts', JSON.stringify(accountsToSave));
            }
        },

        loadAccountsFromStorage() {
            const savedAccounts = localStorage.getItem('duolingoAccounts');
            if (savedAccounts) {
                const accounts = JSON.parse(savedAccounts);
                state.accounts = accounts.filter(acc => acc.ip === state.userIP);
            }
        },

        deleteAllAccounts() {
            state.accounts = [];
            localStorage.removeItem('duolingoAccounts');
            ui.updateLogsTable();
            utils.addNotification('All accounts deleted', 'success');
        },

        deleteAccount(index) {
            if (index >= 0 && index < state.accounts.length) {
                state.accounts.splice(index, 1);
                utils.saveAccountsToStorage();
                ui.updateLogsTable();
                utils.addNotification('Account deleted', 'success');
            }
        },

        updateProgressCounter() {
            if (elements.accountProgress) {
                elements.accountProgress.textContent = ` (${state.createdCount}/${state.totalCount})`;
            }
        }
    };

    const api = {
        checkUsernameAvailability(username, retries = 3) {
            return new Promise((resolve, reject) => {
                const url = `https://www.duolingo.com/2017-06-30/user?username=${encodeURIComponent(username)}`;

                GM_xmlhttpRequest({
                    method: 'GET',
                    url: url,
                    headers: { 'Accept': 'application/json' },
                    onload: function(response) {
                        if (response.status === 200) {
                            try {
                                const data = JSON.parse(response.responseText);
                                resolve(data.available === true);
                            } catch (error) {
                                if (retries > 0) {
                                    setTimeout(() => {
                                        api.checkUsernameAvailability(username, retries - 1).then(resolve).catch(reject);
                                    }, 1000);
                                } else {
                                    reject(new Error(`Invalid response format: ${error.message}`));
                                }
                            }
                        } else if (response.status === 429 && retries > 0) {
                            setTimeout(() => {
                                api.checkUsernameAvailability(username, retries - 1).then(resolve).catch(reject);
                            }, 2000);
                        } else {
                            reject(new Error(`API Error: ${response.status} - ${response.statusText}`));
                        }
                    },
                    onerror: function(error) {
                        if (retries > 0) {
                            setTimeout(() => {
                                api.checkUsernameAvailability(username, retries - 1).then(resolve).catch(reject);
                            }, 2000);
                        } else {
                            reject(new Error(`Network error: ${error.message || 'Unknown error'}`));
                        }
                    }
                });
            });
        },

        createAccount(email, username, password, retries = 3) {
            return new Promise((resolve, reject) => {
                const url = 'https://www.duolingo.com/2017-06-30/user';
                const payload = {
                    email: email,
                    username: username,
                    password: password,
                    fromLanguage: 'en',
                    learningLanguage: 'es'
                };

                GM_xmlhttpRequest({
                    method: 'POST',
                    url: url,
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'application/json'
                    },
                    data: JSON.stringify(payload),
                    onload: function(response) {
                        if (response.status >= 200 && response.status < 300) {
                            resolve(response.responseText);
                        } else if (response.status === 429 && retries > 0) {
                            setTimeout(() => {
                                api.createAccount(email, username, password, retries - 1).then(resolve).catch(reject);
                            }, 2000);
                        } else {
                            reject(new Error(`API Error: ${response.status} - ${response.statusText}`));
                        }
                    },
                    onerror: function(error) {
                        if (retries > 0) {
                            setTimeout(() => {
                                api.createAccount(email, username, password, retries - 1).then(resolve).catch(reject);
                            }, 2000);
                        } else {
                            reject(new Error(`Network error: ${error.message || 'Unknown error'}`));
                        }
                    }
                });
            });
        }
    };

    const ui = {
        createMainContainer() {
            const container = document.createElement('div');
            container.id = 'duolingo-account-creator';
            Object.assign(container.style, {
                position: 'fixed',
                top: '100px',
                left: '20px',
                zIndex: '9999',
                backgroundColor: 'var(--bg-color)',
                padding: '12px',
                borderRadius: '12px',
                boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
                fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif',
                width: '280px',
                cursor: 'move',
                userSelect: 'none',
                border: '1px solid var(--border-color)',
                color: 'var(--text-color)'
            });

            const header = document.createElement('div');
            header.style.display = 'flex';
            header.style.justifyContent = 'space-between';
            header.style.alignItems = 'center';
            header.style.marginBottom = '12px';

            const title = document.createElement('h3');
            title.textContent = 'Account Creator';
            Object.assign(title.style, {
                margin: '0',
                color: 'var(--primary-color)',
                fontSize: '15px',
                fontWeight: '600'
            });

            const buttonGroup = document.createElement('div');
            buttonGroup.style.display = 'flex';
            buttonGroup.style.gap = '4px';

            const settingsBtn = utils.createIconButton('⚙', 'transparent', 'var(--primary-color)', 'Settings');
            settingsBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                this.showSettings();
            });

            const logsBtn = utils.createIconButton('📋', 'transparent', 'var(--info-color)', 'Show Logs');
            logsBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                this.showLogs();
            });

            const notifyBtn = utils.createIconButton('🔔', 'transparent', 'var(--warning-color)', 'Notifications');
            notifyBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                this.showNotifications();
            });

            const closeBtn = utils.createIconButton('×', 'transparent', 'var(--error-color)', 'Close');
            closeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                container.style.display = 'none';
                utils.closeAllMenus();
            });

            buttonGroup.appendChild(settingsBtn);
            buttonGroup.appendChild(logsBtn);
            buttonGroup.appendChild(notifyBtn);
            buttonGroup.appendChild(closeBtn);
            header.appendChild(title);
            header.appendChild(buttonGroup);

            const content = document.createElement('div');
            content.innerHTML = `
                <div style="margin-bottom: 12px;">
                    <label for="account-count" style="display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500; color: var(--secondary-text);">
                        Number of accounts
                        <span id="account-progress" style="color: var(--primary-color); font-size: 12px;"> (0/0)</span>
                    </label>
                    <input type="number" id="account-count" min="1" max="${MAX_ACCOUNTS}" value="1" style="width: 100%; padding: 8px 10px; box-sizing: border-box; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 8px; font-size: 13px; outline: none; transition: all 0.2s;" onkeypress="return (event.charCode !=8 && event.charCode == 0 || (event.charCode >= 48 && event.charCode <= 57))" oninput="if(this.value > ${MAX_ACCOUNTS}) {this.value = ${MAX_ACCOUNTS};}">
                </div>
                <div style="margin-bottom: 12px;">
                    <label for="email-domain" style="display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500; color: var(--secondary-text);">Email Domain</label>
                    <select id="email-domain" style="width: 100%; padding: 8px 10px; box-sizing: border-box; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 8px; font-size: 13px; outline: none;">
                        <option value="gmail.com">gmail.com</option>
                        <option value="yahoo.com">yahoo.com</option>
                        <option value="outlook.com">outlook.com</option>
                        <option value="protonmail.com">protonmail.com</option>
                        <option value="custom">Custom Domain</option>
                    </select>
                    <input type="text" id="custom-domain" placeholder="Enter custom domain" style="width: 100%; padding: 8px 10px; margin-top: 6px; box-sizing: border-box; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 8px; font-size: 13px; outline: none; display: none;">
                </div>
            `;

            const createBtn = document.createElement('button');
            createBtn.id = 'start-create';
            createBtn.textContent = 'Start Creating';
            Object.assign(createBtn.style, {
                width: '100%',
                padding: '10px 0',
                borderRadius: '8px',
                border: 'none',
                backgroundColor: 'var(--primary-color)',
                color: 'white',
                fontSize: '13px',
                fontWeight: '600',
                cursor: 'pointer',
                transition: 'all 0.2s',
                boxShadow: '0 3px 0 0 var(--primary-shadow)',
                position: 'relative',
                overflow: 'hidden'
            });

            createBtn.addEventListener('mouseover', () => {
                createBtn.style.backgroundColor = 'var(--primary-hover)';
                createBtn.style.boxShadow = '0 3px 0 0 var(--primary-shadow)';
            });

            createBtn.addEventListener('mouseout', () => {
                createBtn.style.backgroundColor = 'var(--primary-color)';
                createBtn.style.boxShadow = '0 3px 0 0 var(--primary-shadow)';
            });

            createBtn.addEventListener('mousedown', () => {
                createBtn.style.transform = 'translateY(2px)';
                createBtn.style.boxShadow = '0 1px 0 0 var(--primary-shadow)';
            });

            createBtn.addEventListener('mouseup', () => {
                createBtn.style.transform = 'translateY(0)';
                createBtn.style.boxShadow = '0 3px 0 0 var(--primary-shadow)';
            });

            createBtn.addEventListener('click', accountManager.startCreatingAccounts);

            content.appendChild(createBtn);
            container.appendChild(header);
            container.appendChild(content);
            document.body.appendChild(container);

            document.getElementById('email-domain').addEventListener('change', function() {
                const customDomainInput = document.getElementById('custom-domain');
                customDomainInput.style.display = this.value === 'custom' ? 'block' : 'none';
            });

            utils.makeDraggable(container, header);
            elements.mainContainer = container;
            elements.accountProgress = document.getElementById('account-progress');
        },

        showSettings() {
            if (state.activeMenu === 'settings') {
                utils.closeAllMenus();
                return;
            }
            utils.closeAllMenus();
            state.activeMenu = 'settings';

            if (elements.settingsPanel) {
                elements.settingsPanel.style.display = 'block';
                return;
            }

            const panel = document.createElement('div');
            panel.id = 'settings-panel';
            Object.assign(panel.style, {
                position: 'fixed',
                right: '20px',
                top: '100px',
                backgroundColor: 'var(--panel-color)',
                borderRadius: '12px',
                padding: '12px',
                width: '220px',
                boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
                zIndex: '10000',
                border: '1px solid var(--border-color)',
                display: 'block'
            });

            const panelHeader = document.createElement('div');
            panelHeader.style.display = 'flex';
            panelHeader.style.justifyContent = 'space-between';
            panelHeader.style.alignItems = 'center';
            panelHeader.style.marginBottom = '10px';

            const panelTitle = document.createElement('div');
            panelTitle.textContent = 'Settings';
            panelTitle.style.fontWeight = '600';
            panelTitle.style.color = 'var(--primary-color)';

            const closeBtn = utils.createIconButton('×', 'transparent', 'var(--error-color)', 'Close');
            closeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                panel.style.display = 'none';
                state.activeMenu = null;
            });

            panelHeader.appendChild(panelTitle);
            panelHeader.appendChild(closeBtn);
            panel.appendChild(panelHeader);

            panel.innerHTML += `
                <div style="margin-bottom: 12px;">
                    <div style="font-size: 12px; font-weight: 500; margin-bottom: 6px; color: var(--secondary-text);">Account Options</div>
                    <label style="display: flex; align-items: center; gap: 6px; font-size: 12px; margin-bottom: 4px; cursor: pointer; color: var(--text-color);">
                        <input type="checkbox" id="save-accounts" ${state.settings.saveAccounts ? 'checked' : ''}> Save accounts
                    </label>
                    <label style="display: flex; align-items: center; gap: 6px; font-size: 12px; margin-bottom: 4px; cursor: pointer; color: var(--text-color);">
                        <input type="checkbox" id="random-email" ${state.settings.randomEmail ? 'checked' : ''}> Random emails
                    </label>
                    <label style="display: flex; align-items: center; gap: 6px; font-size: 12px; margin-bottom: 4px; cursor: pointer; color: var(--text-color);">
                        <input type="checkbox" id="random-password" ${state.settings.randomPassword ? 'checked' : ''}> Random passwords
                    </label>
                </div>
                <div style="margin-bottom: 12px;">
                    <div style="font-size: 12px; font-weight: 5
                    00; margin-bottom: 6px; color: var(--secondary-text);">Creation Speed</div>
                    <select id="creation-speed" style="width: 100%; padding: 6px; border-radius: 6px; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--border-color); font-size: 12px;">
                        <option value="fast" ${state.settings.creationSpeed === 'fast' ? 'selected' : ''}>Fast (1s)</option>
                        <option value="medium" ${state.settings.creationSpeed === 'medium' ? 'selected' : ''}>Medium (3s)</option>
                        <option value="slow" ${state.settings.creationSpeed === 'slow' ? 'selected' : ''}>Slow (5s)</option>
                    </select>
                </div>
                <div style="margin-bottom: 12px;">
                    <div style="font-size: 12px; font-weight: 500; margin-bottom: 6px; color: var(--secondary-text);">Theme</div>
                    <select id="theme-selector" style="width: 100%; padding: 6px; border-radius: 6px; background-color: var(--input-bg); color: var(--text-color); border: 1px solid var(--border-color); font-size: 12px;">
                        <option value="dark" ${state.settings.theme === 'dark' ? 'selected' : ''}>Dark Mode</option>
                        <option value="light" ${state.settings.theme === 'light' ? 'selected' : ''}>Light Mode</option>
                    </select>
                </div>
                <button id="save-settings" style="width: 100%; padding: 8px; background-color: var(--primary-color); color: white; border: none; border-radius: 6px; font-size: 12px; cursor: pointer; margin-top: 6px;">Save Settings</button>
            `;

            document.body.appendChild(panel);
            elements.settingsPanel = panel;

            document.getElementById('save-settings').addEventListener('click', () => {
                state.settings = {
                    saveAccounts: document.getElementById('save-accounts').checked,
                    randomEmail: document.getElementById('random-email').checked,
                    randomPassword: document.getElementById('random-password').checked,
                    creationSpeed: document.getElementById('creation-speed').value,
                    theme: document.getElementById('theme-selector').value
                };

                utils.saveSettings();
                utils.addNotification('Settings saved successfully', 'success');
                panel.style.display = 'none';
                state.activeMenu = null;
            });
        },

        showLogs() {
            if (state.activeMenu === 'logs') {
                utils.closeAllMenus();
                return;
            }
            utils.closeAllMenus();
            state.activeMenu = 'logs';

            if (elements.logsPanel) {
                elements.logsPanel.style.display = 'block';
                this.updateLogsTable();
                return;
            }

            const panel = document.createElement('div');
            panel.id = 'logs-panel';
            Object.assign(panel.style, {
                position: 'fixed',
                right: '20px',
                top: '100px',
                backgroundColor: 'var(--bg-color)',
                borderRadius: '12px',
                padding: '12px',
                width: '500px',
                maxHeight: '500px',
                overflowY: 'auto',
                boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
                zIndex: '10000',
                border: '1px solid var(--border-color)',
                color: 'var(--text-color)',
                display: 'block'
            });

            const header = document.createElement('div');
            header.style.display = 'flex';
            header.style.justifyContent = 'space-between';
            header.style.alignItems = 'center';
            header.style.marginBottom = '12px';

            const title = document.createElement('h3');
            title.textContent = 'Created Accounts';
            Object.assign(title.style, {
                margin: '0',
                color: 'var(--info-color)',
                fontSize: '15px',
                fontWeight: '600'
            });

            const buttonGroup = document.createElement('div');
            buttonGroup.style.display = 'flex';
            buttonGroup.style.gap = '4px';

            const deleteAllBtn = utils.createIconButton('🗑️', 'transparent', 'var(--error-color)', 'Delete All');
            deleteAllBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                if (confirm('Are you sure you want to delete ALL accounts?')) {
                    utils.deleteAllAccounts();
                }
            });

            const closeBtn = utils.createIconButton('×', 'transparent', 'var(--error-color)', 'Close');
            closeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                panel.style.display = 'none';
                state.activeMenu = null;
            });

            buttonGroup.appendChild(deleteAllBtn);
            buttonGroup.appendChild(closeBtn);

            header.appendChild(title);
            header.appendChild(buttonGroup);
            panel.appendChild(header);

            const tableContainer = document.createElement('div');
            tableContainer.style.overflowX = 'auto';

            const table = document.createElement('table');
            table.style.width = '100%';
            table.style.borderCollapse = 'collapse';
            table.style.marginTop = '8px';
            table.style.fontSize = '12px';

            const thead = document.createElement('thead');
            thead.innerHTML = `
                <tr style="background-color: var(--panel-color); color: var(--secondary-text);">
                    <th style="padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-color);">#</th>
                    <th style="padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-color);">Email</th>
                    <th style="padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-color);">Username</th>
                    <th style="padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-color);">Password</th>
                    <th style="padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-color);">Status</th>
                    <th style="padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-color);">Actions</th>
                </tr>
            `;
            table.appendChild(thead);

            const tbody = document.createElement('tbody');
            tbody.id = 'logs-table-body';
            table.appendChild(tbody);

            tableContainer.appendChild(table);
            panel.appendChild(tableContainer);

            const exportBtn = document.createElement('button');
            exportBtn.textContent = 'Export to CSV';
            Object.assign(exportBtn.style, {
                marginTop: '12px',
                padding: '8px 12px',
                backgroundColor: 'var(--primary-color)',
                color: 'white',
                border: 'none',
                borderRadius: '6px',
                cursor: 'pointer',
                fontSize: '12px',
                width: '100%'
            });
            exportBtn.addEventListener('click', this.exportToCSV);

            panel.appendChild(exportBtn);
            document.body.appendChild(panel);
            elements.logsPanel = panel;

            this.updateLogsTable();
        },

        showNotifications() {
            if (state.activeMenu === 'notifications') {
                utils.closeAllMenus();
                return;
            }
            utils.closeAllMenus();
            state.activeMenu = 'notifications';

            if (elements.notificationsPanel) {
                elements.notificationsPanel.style.display = 'block';
                this.updateNotificationsPanel();
                return;
            }

            const panel = document.createElement('div');
            panel.id = 'notifications-panel';
            Object.assign(panel.style, {
                position: 'fixed',
                right: '20px',
                top: '100px',
                backgroundColor: 'var(--bg-color)',
                borderRadius: '12px',
                padding: '12px',
                width: '300px',
                maxHeight: '400px',
                overflowY: 'auto',
                boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
                zIndex: '10000',
                border: '1px solid var(--border-color)',
                color: 'var(--text-color)',
                display: 'block'
            });

            const header = document.createElement('div');
            header.style.display = 'flex';
            header.style.justifyContent = 'space-between';
            header.style.alignItems = 'center';
            header.style.marginBottom = '12px';

            const title = document.createElement('h3');
            title.textContent = 'Notifications';
            Object.assign(title.style, {
                margin: '0',
                color: 'var(--warning-color)',
                fontSize: '15px',
                fontWeight: '600'
            });

            const closeBtn = utils.createIconButton('×', 'transparent', 'var(--error-color)', 'Close');
            closeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                panel.style.display = 'none';
                state.activeMenu = null;
            });

            const clearBtn = utils.createIconButton('🗑️', 'transparent', 'var(--error-color)', 'Clear All');
            clearBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                state.notifications = [];
                this.updateNotificationsPanel();
            });

            const btnGroup = document.createElement('div');
            btnGroup.style.display = 'flex';
            btnGroup.appendChild(clearBtn);
            btnGroup.appendChild(closeBtn);

            header.appendChild(title);
            header.appendChild(btnGroup);
            panel.appendChild(header);

            const notificationsContainer = document.createElement('div');
            notificationsContainer.id = 'notifications-container';
            panel.appendChild(notificationsContainer);

            document.body.appendChild(panel);
            elements.notificationsPanel = panel;

            this.updateNotificationsPanel();
        },

        updateNotificationsPanel() {
            const container = document.getElementById('notifications-container');
            if (!container) return;

            container.innerHTML = '';

            if (state.notifications.length === 0) {
                const emptyMsg = document.createElement('div');
                emptyMsg.textContent = 'No notifications yet';
                emptyMsg.style.color = 'var(--secondary-text)';
                emptyMsg.style.textAlign = 'center';
                emptyMsg.style.padding = '12px';
                container.appendChild(emptyMsg);
                return;
            }

            state.notifications.forEach(notification => {
                const notifElement = document.createElement('div');
                notifElement.style.padding = '8px';
                notifElement.style.marginBottom = '6px';
                notifElement.style.borderRadius = '6px';
                notifElement.style.backgroundColor = 'var(--panel-color)';
                notifElement.style.borderLeft = `3px solid ${
                    notification.type === 'success' ? 'var(--primary-color)' :
                    notification.type === 'error' ? 'var(--error-color)' : 'var(--info-color)'
                }`;

                const timeElement = document.createElement('div');
                timeElement.textContent = notification.timestamp;
                timeElement.style.fontSize = '10px';
                timeElement.style.color = 'var(--secondary-text)';
                timeElement.style.marginBottom = '4px';

                const messageElement = document.createElement('div');
                messageElement.textContent = notification.message;
                messageElement.style.fontSize = '12px';

                notifElement.appendChild(timeElement);
                notifElement.appendChild(messageElement);
                container.appendChild(notifElement);
            });
        },

        updateLogsTable() {
            const tbody = document.getElementById('logs-table-body');
            if (!tbody) return;

            tbody.innerHTML = '';

            if (state.accounts.length === 0) {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td colspan="6" style="padding: 12px; text-align: center; color: var(--secondary-text);">No accounts created yet</td>
                `;
                tbody.appendChild(row);
                return;
            }

            state.accounts.forEach((account, index) => {
                const row = document.createElement('tr');
                row.className = 'account-row';
                Object.assign(row.style, {
                    borderBottom: '1px solid var(--border-color)',
                    backgroundColor: index % 2 === 0 ? 'var(--bg-color)' : 'var(--panel-color)'
                });

                const deleteBtn = document.createElement('button');
                deleteBtn.className = 'delete-account-btn';
                deleteBtn.textContent = 'Delete';
                deleteBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    utils.deleteAccount(index);
                });

                const actionCell = document.createElement('td');
                actionCell.style.padding = '6px 8px';
                actionCell.appendChild(deleteBtn);

                row.innerHTML = `
                    <td style="padding: 6px 8px;">${index + 1}</td>
                    <td style="padding: 6px 8px;">${account.email || ''}</td>
                    <td style="padding: 6px 8px;">${account.username || ''}</td>
                    <td style="padding: 6px 8px;">${account.password || ''}</td>
                    <td style="padding: 6px 8px; color: ${account.status === 'Success' ? 'var(--primary-color)' : 'var(--error-color)'}">${account.status || ''}</td>
                `;
                row.appendChild(actionCell);
                tbody.appendChild(row);
            });
        },

        exportToCSV() {
            if (state.accounts.length === 0) {
                utils.addNotification('No accounts to export', 'error');
                return;
            }

            let csvContent = "STT,Email,Username,Password,Status\n";
            state.accounts.forEach((account, index) => {
                csvContent += `${index + 1},${account.email || ''},${account.username || ''},${account.password || ''},${account.status || ''}\n`;
            });

            const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.setAttribute('href', url);
            link.setAttribute('download', `duolingo_accounts_${new Date().toISOString().slice(0,10)}.csv`);
            link.style.display = 'none';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            utils.addNotification('Accounts exported to CSV', 'success');
        },

        addToggleButton() {
            const toggleBtn = document.createElement('div');
            toggleBtn.textContent = 'AC';
            Object.assign(toggleBtn.style, {
                position: 'fixed',
                bottom: '20px',
                right: '20px',
                zIndex: '9998',
                backgroundColor: 'var(--primary-color)',
                color: 'white',
                width: '36px',
                height: '36px',
                borderRadius: '50%',
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                cursor: 'pointer',
                boxShadow: '0 3px 6px rgba(0,0,0,0.2)',
                fontWeight: 'bold',
                fontSize: '13px',
                transition: 'all 0.2s'
            });

            toggleBtn.addEventListener('mouseover', () => {
                toggleBtn.style.transform = 'scale(1.1)';
                toggleBtn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
            });

            toggleBtn.addEventListener('mouseout', () => {
                toggleBtn.style.transform = 'scale(1)';
                toggleBtn.style.boxShadow = '0 3px 6px rgba(0,0,0,0.2)';
            });

            toggleBtn.addEventListener('click', () => {
                if (elements.mainContainer) {
                    elements.mainContainer.style.display = elements.mainContainer.style.display === 'none' ? 'block' : 'none';
                    if (elements.mainContainer.style.display === 'none') {
                        utils.closeAllMenus();
                    }
                }
            });

            document.body.appendChild(toggleBtn);
        }
    };

    const accountManager = {
        async startCreatingAccounts() {
            const count = parseInt(document.getElementById('account-count').value);
            const button = document.getElementById('start-create');

            if (isNaN(count) || count <= 0) {
                utils.addNotification('Invalid account count', 'error');
                return;
            }

            if (count > MAX_ACCOUNTS) {
                utils.addNotification(`Cannot create more than ${MAX_ACCOUNTS} accounts`, 'error');
                return;
            }

            state.isCreating = true;
            state.createdCount = 0;
            state.totalCount = count;
            utils.updateProgressCounter();
            button.disabled = true;
            button.style.backgroundColor = 'var(--button-bg)';
            button.style.boxShadow = '0 3px 0 0 var(--border-color)';
            button.textContent = 'Creating...';

            utils.addNotification(`Starting creation of ${count} accounts`, 'info');
            state.accounts = [];

            let created = 0;
            let successCount = 0;
            let errorCount = 0;

            const delay = {
                fast: 1000,
                medium: 3000,
                slow: 5000
            }[state.settings.creationSpeed] || 3000;

            while (created < count && state.isCreating) {
                try {
                    const email = state.settings.randomEmail ? utils.generateRandomEmail() : '';
                    let username = utils.generateRandomUsername();
                    const password = state.settings.randomPassword ? utils.generateRandomPassword() : 'defaultPassword123!';

                    let isAvailable = false;
                    let attempts = 0;
                    const maxAttempts = 5;

                    while (!isAvailable && attempts < maxAttempts && state.isCreating) {
                        try {
                            isAvailable = await api.checkUsernameAvailability(username);
                            if (!isAvailable) {
                                attempts++;
                                username = utils.generateRandomUsername();
                                await new Promise(resolve => setTimeout(resolve, 500));
                            }
                        } catch (error) {
                            console.error('Error checking username:', error);
                            attempts++;
                            await new Promise(resolve => setTimeout(resolve, 500));
                        }
                    }

                    if (!isAvailable) {
                        state.accounts.push({
                            email,
                            username,
                            password,
                            status: 'Failed: No available username',
                            ip: state.userIP,
                            createdAt: new Date().toISOString()
                        });
                        utils.saveAccountsToStorage();
                        utils.addNotification('Failed to create account: No available username', 'error');
                        errorCount++;
                        created++;
                        state.createdCount++;
                        utils.updateProgressCounter();
                        ui.updateLogsTable();
                        continue;
                    }

                    state.accounts.push({
                        email,
                        username,
                        password,
                        status: 'Creating...',
                        ip: state.userIP,
                        createdAt: new Date().toISOString()
                    });
                    utils.saveAccountsToStorage();
                    ui.updateLogsTable();

                    await api.createAccount(email, username, password)
                        .then(() => {
                            state.accounts[created].status = 'Success';
                            utils.saveAccountsToStorage();
                            utils.addNotification(`Account created: ${username}`, 'success');
                            successCount++;
                        })
                        .catch(error => {
                            state.accounts[created].status = 'Failed: ' + error.message;
                            utils.saveAccountsToStorage();
                            utils.addNotification(`Failed to create account: ${error.message}`, 'error');
                            errorCount++;
                        });

                    created++;
                    state.createdCount++;
                    utils.updateProgressCounter();
                    ui.updateLogsTable();

                    if (created < count) {
                        await new Promise(resolve => setTimeout(resolve, delay));
                    }
                } catch (error) {
                    console.error('Error in account creation loop:', error);
                    created++;
                    state.createdCount++;
                    utils.updateProgressCounter();
                    errorCount++;
                    utils.addNotification(`Error during account creation: ${error.message}`, 'error');
                }
            }

            button.disabled = false;
            button.style.backgroundColor = 'var(--primary-color)';
            button.style.boxShadow = '0 3px 0 0 var(--primary-shadow)';
            button.textContent = 'Start Creating';
            state.isCreating = false;

            utils.addNotification(`Finished creating ${count} accounts (${successCount} success)`, successCount > 0 ? 'success' : 'error');
        }
    };

    async function init() {
        state.userIP = await utils.getUserIP();
        utils.loadAccountsFromStorage();
        ui.createMainContainer();
        ui.addToggleButton();
        utils.applyTheme();
        utils.addNotification('Account Creator initialized', 'info');
    }

    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址