Greasy Fork

Duolingo SM

[Demo] Auto Farm XP, streak, gem hack – so easy!

// ==UserScript==
// @name         Duolingo SM
// @version      2.3.0
// @author       MeowWoof
// @namespace    http://tampermonkey.net/
// @description  [Demo] Auto Farm XP, streak, gem hack – so easy!
// @match        https://*.duolingo.com/*
// @grant        none
// @license      MIT
// @icon         https://d35aaqx5ub95lt.cloudfront.net/vendor/a0ee30fa22ca3d00e9e5db913b1965b5.svg
// ==/UserScript==

(function() {
    'use strict';

    // đừng đọc mà con cạc
    const sessionUrl = "https://www.duolingo.com/2017-06-30/sessions";
    let isFarming = false, isVisible = true;
    let currentLanguage = "en";

    const getJwtToken = () => document.cookie.split(';').find(c => c.trim().startsWith('jwt_token='))?.split('=')[1] || null;
    const decodeJwtToken = token => JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
    const formatHeaders = jwtToken => ({ "Content-Type": "application/json", "Authorization": `Bearer ${jwtToken}`, "User-Agent": navigator.userAgent });
    const getUserInfo = async (sub, headers) => (await fetch(`https://www.duolingo.com/2017-06-30/users/${sub}?fields=username,fromLanguage,learningLanguage`, { headers })).json();

    const farmXp = async (headers, sessionPayload, updateSessionPayload) => {
      while (isFarming) {
        try {
          const session = await (await fetch(sessionUrl, { method: 'POST', headers, body: JSON.stringify(sessionPayload) })).json();
          const updatedSession = await (await fetch(`${sessionUrl}/${session.id}`, { method: 'PUT', headers, body: JSON.stringify({ ...session, ...updateSessionPayload }) })).json();
          document.getElementById("_xpAmount").innerText = parseInt(document.getElementById("_xpAmount").innerText) + updatedSession.xpGain;
        } catch (error) {
          alert(currentLanguage === "en" ? "An error occurred while farming XP. Please try again!" : "Đã xảy ra lỗi khi farm XP. Vui lòng thử lại!");
          await new Promise(resolve => setTimeout(resolve, 5000));
        }
      }
    };

    const translations = {
        en: {
            title: "Duolingo SM",
            welcome: "Welcome to Duolingo SM!",
            notLoggedIn: "You are not logged in! (if you are logged in, please refresh the page)",
            hello: "Hello",
            farmXp: "FARM XP",
            farmGems: "FARM GEMS",
            upgrade: "UPGRADE",
            start: "Start Farming",
            stop: "Stop Farming",
            xpGained: "XP Gained",
            warning: "Please use this tool responsibly.",
            loading: "Processing...",
            switchLang: "Tiếng Việt",
            status: "Status",
            active: "Active",
            inactive: "Inactive"
        },
        vi: {
            title: "Super-Duo",
            welcome: "Chào mừng đến với Duolingo SM!",
            notLoggedIn: "Bạn chưa đăng nhập! (nếu bạn đã đăng nhập, vui lòng tải lại trang)",
            hello: "Xin chào",
            farmXp: "FARM XP",
            farmGems: "FARM GEMS",
            upgrade: "NÂNG CẤP",
            start: "Bắt đầu Farm",
            stop: "Dừng Farm",
            xpGained: "XP Đã Nhận",
            warning: "Vui lòng sử dụng công cụ này 1 cách tự nhiên",
            loading: "Đang xử lý...",
            switchLang: "English",
            status: "Trạng thái",
            active: "Hoạt động",
            inactive: "Không hoạt động"
        }
    };

const initSuperDuolingo = async () => {
    const style = document.createElement('style');
    style.innerHTML = `
:root {
    /* Light theme colors */
    --primary-bg: #ffffff;
    --secondary-bg: #f8fafc;
    --accent-bg: #f1f5f9;
    --surface: #e2e8f0;
    --surface-light: #f8fafc;

    --primary-color: #0ea5e9;
    --secondary-color: #6366f1;
    --accent-color: #8b5cf6;
    --success-color: #10b981;
    --warning-color: #f59e0b;
    --error-color: #ef4444;

    --text-primary: #1e293b;
    --text-secondary: #475569;
    --text-muted: #64748b;

    --gradient-primary: linear-gradient(135deg, #0ea5e9 0%, #3b82f6 100%);
    --gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
    --gradient-secondary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
    --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
    --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
    --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);

    --border-radius: 8px;
    --border-radius-lg: 12px;
    --transition: all 0.2s ease;
}

._sd_container {
    position: fixed;
    right: 16px;
    top: 50%;
    transform: translateY(-50%);
    width: 280px;
    background: var(--primary-bg);
    border-radius: var(--border-radius-lg);
    box-shadow: var(--shadow-xl);
    z-index: 9999;
    overflow: hidden;
    transition: var(--transition);
    border: 1px solid var(--surface);
}

._sd_container.hidden {
    transform: translateY(-50%) translateX(100%);
    opacity: 0;
}

._sd_header {
    background: var(--gradient-primary);
    padding: 16px;
    text-align: center;
}

._sd_title {
    font-size: 1.1rem;
    font-weight: 700;
    color: white;
    margin: 0 0 8px 0;
}

._sd_language_switcher {
    background: rgba(255, 255, 255, 0.2);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: var(--border-radius);
    padding: 4px 12px;
    font-size: 0.75rem;
    color: white;
    cursor: pointer;
    transition: var(--transition);
    font-weight: 500;
}

._sd_language_switcher:hover {
    background: rgba(255, 255, 255, 0.3);
}

._sd_body {
    padding: 16px;
    background: var(--primary-bg);
}

._sd_welcome {
    font-size: 0.85rem;
    color: var(--text-secondary);
    margin-bottom: 16px;
    padding: 12px;
    background: var(--surface-light);
    border-radius: var(--border-radius);
    border-left: 3px solid var(--primary-color);
}

._sd_status_indicator {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 16px;
    padding: 8px 12px;
    background: var(--surface-light);
    border-radius: var(--border-radius);
    font-size: 0.8rem;
}

._sd_status_dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--error-color);
    transition: var(--transition);
}

._sd_status_dot.active {
    background: var(--success-color);
}

._sd_status_text {
    color: var(--text-muted);
    font-weight: 500;
}

._sd_xp_counter {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 10px;
    background: var(--surface-light);
    border-radius: var(--border-radius);
    margin-bottom: 16px;
    border: 1px solid var(--surface);
}

._sd_xp_counter_label {
    font-size: 0.8rem;
    color: var(--text-muted);
    font-weight: 500;
}

._sd_xp_counter_value {
    font-size: 1rem;
    color: var(--primary-color);
    font-weight: 700;
}

._sd_tabs {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 16px;
}

._sd_tab {
    padding: 10px 12px;
    border-radius: var(--border-radius);
    background: var(--surface-light);
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--transition);
    font-weight: 500;
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border: 1px solid var(--surface);
}

._sd_tab:hover {
    background: var(--accent-bg);
    color: var(--text-primary);
    border-color: var(--primary-color);
}

._sd_tab.active {
    background: var(--primary-color);
    color: white;
    border-color: var(--primary-color);
}

._sd_tab_icon {
    font-size: 1rem;
}

._sd_start_btn {
    padding: 12px 20px;
    background: var(--gradient-success);
    border: none;
    border-radius: var(--border-radius);
    color: white;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
    width: 100%;
    font-size: 0.9rem;
    box-shadow: var(--shadow-sm);
}

._sd_start_btn:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

._sd_start_btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

._sd_toggle_btn {
    position: fixed;
    left: 16px;
    bottom: 20px;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--gradient-primary);
    border: none;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    z-index: 10000;
    transition: var(--transition);
    color: white;
    font-weight: bold;
    font-size: 1.2rem;
    box-shadow: var(--shadow-lg);
}

._sd_toggle_btn:hover {
    transform: scale(1.05);
    box-shadow: var(--shadow-xl);
}

._sd_overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 9999;
}

._sd_modal {
    background: var(--primary-bg);
    border-radius: var(--border-radius-lg);
    padding: 24px;
    width: 90%;
    max-width: 320px;
    box-shadow: var(--shadow-xl);
    border: 1px solid var(--surface);
    text-align: center;
}

._sd_modal_title {
    font-size: 1.2rem;
    font-weight: 700;
    margin-bottom: 16px;
    color: var(--text-primary);
}

._sd_spinner {
    display: inline-block;
    width: 40px;
    height: 40px;
    margin: 16px auto;
}

._sd_spinner:after {
    content: " ";
    display: block;
    width: 32px;
    height: 32px;
    margin: 4px;
    border-radius: 50%;
    border: 3px solid var(--surface);
    border-top: 3px solid var(--primary-color);
    animation: _sd_spin 1s linear infinite;
}

._sd_xp_info {
    font-size: 1rem;
    color: var(--primary-color);
    margin: 16px 0;
    font-weight: 600;
    padding: 12px;
    background: var(--surface-light);
    border-radius: var(--border-radius);
    border: 1px solid var(--surface);
}

._sd_warning {
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin-bottom: 16px;
    line-height: 1.4;
    padding: 12px;
    background: #fef3c7;
    border-radius: var(--border-radius);
    border: 1px solid #fbbf24;
}

._sd_stop_btn {
    padding: 10px 20px;
    background: var(--gradient-secondary);
    border: none;
    border-radius: var(--border-radius);
    color: white;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
    font-size: 0.85rem;
    box-shadow: var(--shadow-sm);
}

._sd_stop_btn:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

@keyframes _sd_spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* Responsive */
@media (max-width: 768px) {
    ._sd_container {
        width: 260px;
        right: 12px;
    }

    ._sd_toggle_btn {
        left: 12px;
        bottom: 16px;
        width: 40px;
        height: 40px;
        font-size: 1.1rem;
    }

    ._sd_modal {
        margin: 16px;
        padding: 20px;
    }
}

/* Focus states */
._sd_tab:focus,
._sd_start_btn:focus,
._sd_stop_btn:focus,
._sd_language_switcher:focus,
._sd_toggle_btn:focus {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}
    `;
    document.head.appendChild(style);

    const containerHTML = `
        <div class="_sd_container" id="_sd_container">
            <div class="_sd_header">
                <h2 class="_sd_title" id="_sd_title">${translations[currentLanguage].title}</h2>
                <button class="_sd_language_switcher" id="_sd_language_switcher">
                    ${translations[currentLanguage].switchLang}
                </button>
            </div>
            <div class="_sd_body">
                <div class="_sd_welcome" id="_sd_welcome">
                    ${translations[currentLanguage].welcome}
                </div>

                <div class="_sd_status_indicator">
                    <div class="_sd_status_dot" id="_sd_status_dot"></div>
                    <span class="_sd_status_text" id="_sd_status_text">
                        ${translations[currentLanguage].status}: ${translations[currentLanguage].inactive}
                    </span>
                </div>

                <div class="_sd_xp_counter">
                    <span class="_sd_xp_counter_label" id="_sd_xp_label">${translations[currentLanguage].xpGained}:</span>
                    <span class="_sd_xp_counter_value" id="_xpAmount">0</span>
                    <span class="_sd_xp_counter_label">XP</span>
                </div>

                <div class="_sd_tabs">
                    <div class="_sd_tab active" id="_sd_tab_farm_xp">
                        <span>${translations[currentLanguage].farmXp}</span>
                        <span class="_sd_tab_icon">⚡</span>
                    </div>
                    <div class="_sd_tab" id="_sd_tab_farm_gems">
                        <span>${translations[currentLanguage].farmGems}</span>
                        <span class="_sd_tab_icon">💎</span>
                    </div>
                    <div class="_sd_tab" id="_sd_tab_upgrade">
                        <span>${translations[currentLanguage].upgrade}</span>
                        <span class="_sd_tab_icon">👑</span>
                    </div>
                </div>

                <div class="_sd_farm_controls">
                    <button class="_sd_start_btn" id="_sd_start_btn">
                        ${translations[currentLanguage].start}
                    </button>
                </div>
            </div>
        </div>
        <button class="_sd_toggle_btn" id="_sd_toggle_btn">→</button>
        <div class="_sd_overlay" id="_sd_overlay">
            <div class="_sd_modal">
                <h2 class="_sd_modal_title" id="_sd_modal_title">
                    ${translations[currentLanguage].title}
                </h2>
                <p id="_sd_loading_message">
                    ${translations[currentLanguage].loading}
                </p>
                <div class="_sd_spinner" id="_sd_spinner"></div>
                <div class="_sd_warning" id="_sd_warning">
                    ${translations[currentLanguage].warning}
                </div>
                <div class="_sd_xp_info">
                    <span id="_sd_xp_label_modal">${translations[currentLanguage].xpGained}:</span>
                    <span id="_xpAmount_modal">0</span> XP
                </div>
                <button id="_sd_stop_btn" class="_sd_stop_btn">
                    ${translations[currentLanguage].stop}
                </button>
            </div>
        </div>
    `;
    document.body.insertAdjacentHTML('beforeend', containerHTML);

    const JWT = getJwtToken();
    if (!JWT) {
        document.getElementById("_sd_start_btn").disabled = true;
        document.getElementById("_sd_welcome").innerText = translations[currentLanguage].notLoggedIn;
        return;
    }

    const HEADERS = formatHeaders(JWT);
    try {
        const { username, fromLanguage, learningLanguage } = await getUserInfo(
            decodeJwtToken(JWT).sub,
            HEADERS
        );
        document.getElementById("_sd_welcome").innerHTML =
            `${translations[currentLanguage].hello} <strong>${username}</strong>! 🎉`;

        const sessionPayload = {
            challengeTypes: [
                "assist", "characterIntro", "characterMatch", "characterPuzzle",
                "characterSelect", "characterTrace", "characterWrite",
                "completeReverseTranslation", "definition", "dialogue",
                "extendedMatch", "extendedListenMatch", "form", "freeResponse",
                "gapFill", "judge", "listen", "listenComplete", "listenMatch",
                "match", "name", "listenComprehension", "listenIsolation",
                "listenSpeak", "listenTap", "orderTapComplete", "partialListen",
                "partialReverseTranslate", "patternTapComplete", "radioBinary",
                "radioImageSelect", "radioListenMatch", "radioListenRecognize",
                "radioSelect", "readComprehension", "reverseAssist",
                "sameDifferent", "select", "selectPronunciation",
                "selectTranscription", "svgPuzzle", "syllableTap",
                "syllableListenTap", "speak", "tapCloze", "tapClozeTable",
                "tapComplete", "tapCompleteTable", "tapDescribe", "translate",
                "transliterate", "transliterationAssist", "typeCloze",
                "typeClozeTable", "typeComplete", "typeCompleteTable",
                "writeComprehension"
            ],
            fromLanguage,
            learningLanguage,
            type: "GLOBAL_PRACTICE"
        };

        const updateSessionPayload = {
            heartsLeft: 0,
            startTime: Math.floor(Date.now() / 1000),
            enableBonusPoints: false,
            endTime: Math.floor(Date.now() / 1000) + 112,
            failed: false,
            maxInLessonStreak: 9,
            shouldLearnThings: true
        };

        document.getElementById("_sd_toggle_btn").addEventListener("click", () => {
            isVisible = !isVisible;
            document.getElementById("_sd_container").classList.toggle("hidden", !isVisible);
            document.getElementById("_sd_toggle_btn").innerHTML = isVisible ? "→" : "←";
        });

        document.getElementById("_sd_start_btn").addEventListener("click", () => {
            document.getElementById("_sd_overlay").style.display = "flex";
            document.getElementById("_sd_status_dot").classList.add("active");
            document.getElementById("_sd_status_text").innerText =
                `${translations[currentLanguage].status}: ${translations[currentLanguage].active}`;
            isFarming = true;

            const syncXpCounters = () => {
                const mainXp = document.getElementById("_xpAmount").innerText;
                document.getElementById("_xpAmount_modal").innerText = mainXp;
            };

            const observer = new MutationObserver(syncXpCounters);
            observer.observe(document.getElementById("_xpAmount"), { childList: true, subtree: true });

            farmXp(HEADERS, sessionPayload, updateSessionPayload);
        });

        document.getElementById("_sd_stop_btn").addEventListener("click", () => {
            document.getElementById("_sd_overlay").style.display = "none";
            document.getElementById("_sd_status_dot").classList.remove("active");
            document.getElementById("_sd_status_text").innerText =
                `${translations[currentLanguage].status}: ${translations[currentLanguage].inactive}`;
            isFarming = false;

            const startBtn = document.getElementById("_sd_start_btn");
            startBtn.disabled = true;
            startBtn.innerText = "...";
            setTimeout(() => {
                startBtn.disabled = false;
                startBtn.innerText = translations[currentLanguage].start;
            }, 2000);
        });

        document.getElementById("_sd_language_switcher").addEventListener("click", () => {
            currentLanguage = currentLanguage === "en" ? "vi" : "en";
            updateLanguage();
        });

        document.getElementById("_sd_tab_farm_gems").addEventListener("click", () => {
            window.open("https://discord.gg/ufBrcGemBH", "_blank");
        });

        document.getElementById("_sd_tab_upgrade").addEventListener("click", () => {
            window.open("https://duolingo.click", "_blank");
        });

    } catch (error) {
        console.error("Error initializing SuperDuolingo:", error);
        document.getElementById("_sd_start_btn").disabled = true;
        document.getElementById("_sd_welcome").innerText = translations[currentLanguage].notLoggedIn;
    }
};
const updateLanguage = () => {
    const elements = {
        "_sd_title": translations[currentLanguage].title,
        "_sd_welcome": translations[currentLanguage].welcome,
        "_sd_language_switcher": translations[currentLanguage].switchLang,
        "_sd_start_btn": translations[currentLanguage].start,
        "_sd_xp_label": translations[currentLanguage].xpGained,
        "_sd_xp_label_modal": translations[currentLanguage].xpGained,
        "_sd_modal_title": translations[currentLanguage].title,
        "_sd_loading_message": translations[currentLanguage].loading,
        "_sd_warning": translations[currentLanguage].warning,
        "_sd_stop_btn": translations[currentLanguage].stop
    };

    Object.entries(elements).forEach(([id, text]) => {
        const element = document.getElementById(id);
        if (element) element.innerText = text;
    });

    document.querySelector("#_sd_tab_farm_xp span").innerText = translations[currentLanguage].farmXp;
    document.querySelector("#_sd_tab_farm_gems span").innerText = translations[currentLanguage].farmGems;
    document.querySelector("#_sd_tab_upgrade span").innerText = translations[currentLanguage].upgrade;

    const statusDot = document.getElementById("_sd_status_dot");
    const statusText = document.getElementById("_sd_status_text");
    const isActive = statusDot.classList.contains("active");
    statusText.innerText = `${translations[currentLanguage].status}: ${isActive ? translations[currentLanguage].active : translations[currentLanguage].inactive}`;
};

const addTabClickEvents = () => {
    document.querySelectorAll("._sd_tab").forEach(tab => {
        tab.addEventListener("click", () => {
            document.querySelectorAll("._sd_tab").forEach(t => t.classList.remove("active"));
            tab.classList.add("active");
        });
    });
};

const addKeyboardShortcuts = () => {
    document.addEventListener("keydown", (e) => {
        if (e.ctrlKey && e.shiftKey && e.key === 'D') {
            e.preventDefault();
            document.getElementById("_sd_toggle_btn").click();
        }

        if (e.key === 'Escape' && document.getElementById("_sd_overlay").style.display === "flex") {
            document.getElementById("_sd_stop_btn").click();
        }
    });
};

const addSoundEffects = () => {
    const playSound = (frequency, duration) => {
        if (typeof AudioContext !== 'undefined' || typeof webkitAudioContext !== 'undefined') {
            const audioContext = new (window.AudioContext || window.webkitAudioContext)();
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();

            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);

            oscillator.frequency.value = frequency;
            oscillator.type = 'sine';

            gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration);

            oscillator.start(audioContext.currentTime);
            oscillator.stop(audioContext.currentTime + duration);
        }
    };

    const originalXpElement = document.getElementById("_xpAmount");
    if (originalXpElement) {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' || mutation.type === 'characterData') {
                    const newValue = parseInt(mutation.target.textContent || mutation.target.innerText);
                    if (newValue > 0 && isFarming) {
                        playSound(800, 0.2);
                    }
                }
            });
        });
        observer.observe(originalXpElement, { childList: true, subtree: true, characterData: true });
    }
};

const addAnimations = () => {
    const xpCounter = document.getElementById("_xpAmount");
    if (xpCounter) {
        const observer = new MutationObserver(() => {
            xpCounter.style.transform = "scale(1.2)";
            xpCounter.style.color = "var(--success-color)";
            setTimeout(() => {
                xpCounter.style.transform = "scale(1)";
                xpCounter.style.color = "var(--primary-color)";
            }, 300);
        });
        observer.observe(xpCounter, { childList: true, subtree: true });
    }
};

const addTooltips = () => {
    const tooltips = {
        "_sd_tab_farm_xp": "Farm XP automatically to level up faster",
        "_sd_tab_farm_gems": "Join our Discord community for gem farming",
        "_sd_tab_upgrade": "Upgrade to Pro version for more features",
        "_sd_toggle_btn": "Toggle SuperDuolingo panel (Ctrl+Shift+D)"
    };

    Object.entries(tooltips).forEach(([id, text]) => {
        const element = document.getElementById(id);
        if (element) {
            element.title = text;
        }
    });
};

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

    setTimeout(() => {
        addTabClickEvents();
        addKeyboardShortcuts();
        addSoundEffects();
        addAnimations();
        addTooltips();

        const xpElement = document.getElementById("_xpAmount");
        if (xpElement) {
            const observer = new MutationObserver(() => {
                const currentXp = parseInt(xpElement.innerText) || 0;
            });
            observer.observe(xpElement, { childList: true, subtree: true });
        }
    }, 1000);
};

let inactivityTimer;
const resetInactivityTimer = () => {
    clearTimeout(inactivityTimer);
    inactivityTimer = setTimeout(() => {
        if (!isFarming && isVisible) {
            document.getElementById("_sd_toggle_btn").click();
        }
    }, 300000);
};

['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'].forEach(event => {
    document.addEventListener(event, resetInactivityTimer, true);
});

const logError = (error, context) => {
    console.error(`[SuperDuolingo] Error in ${context}:`, error);
};

if (typeof module !== 'undefined' && module.exports) {
    module.exports = {
        initSuperDuolingo,
        updateLanguage,
        farmXp,
        getJwtToken,
        formatHeaders
    };
}

init();

})();