Leetcode Addons

Code Autocomplete, complecity Analyzer, More Stats in profile and more updates coming.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

Advertisement:

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

Advertisement:

// ==UserScript==
// @name         Leetcode Addons
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  Code Autocomplete, complecity Analyzer, More Stats in profile and more updates coming.
// @author       Nikhil Nainala
// @match        https://leetcode.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license MIT 
// ==/UserScript==

(function () {
    'use strict';

    // SETTINGS
    const SETTINGS = {
        autocomplete: true,
        complexityButton: true,
        questionsPerDay: true
    };



    // AutoComplete
    function enableAutocomplete() {
        if (!window.monaco) return false;

        const editor = window.monaco.editor.getEditors?.()?.[0];
        const model = window.monaco.editor.getModels?.()?.[0];

        if (!editor || !model) return false;

        editor.updateOptions({
            quickSuggestions: {
                other: true,
                comments: false,
                strings: false
            },
            suggestOnTriggerCharacters: true,
            tabCompletion: "on",
            acceptSuggestionOnEnter: "on"
        });

        if (window.__leetcodeAddonsAutocomplete) return true;
        window.__leetcodeAddonsAutocomplete = true;

        monaco.languages.registerCompletionItemProvider(
            model.getLanguageId(),
            {
                provideCompletionItems(model, position) {
                    const currentWord =
                          model.getWordUntilPosition(position);

                    const prefix = currentWord.word;

                    if (!prefix) {
                        return { suggestions: [] };
                    }

                    const text = model.getValue();

                    const words = [
                        ...new Set(
                            text.match(/[A-Za-z_][A-Za-z0-9_]*/g) || []
                        )
                    ];

                    const suggestions = words
                    .filter(
                        w =>
                        w.startsWith(prefix) &&
                        w !== prefix
                    )
                    .map(word => ({
                        label: word,
                        kind: monaco.languages
                        .CompletionItemKind.Text,
                        insertText: word,
                        range: {
                            startLineNumber:
                            position.lineNumber,
                            endLineNumber:
                            position.lineNumber,
                            startColumn:
                            currentWord.startColumn,
                            endColumn:
                            currentWord.endColumn
                        }
                    }));

                    return { suggestions };
                }
            }
        );

        return true;
    }

    // COMPLEXITY BUTTON
    function createDialog(time, space, message) {
        // Overlay
        const overlay = document.createElement("div");
        overlay.className = "modal-overlay";

        // Modal
        const modal = document.createElement("div");
        modal.className = "modal";

        modal.innerHTML = `
        <div class="modal-header">
            <h2>Leetcode Complexity Analyzer</h2>
        </div>
        <div class="modal-body">
            <p>Time Complexity: ${time}</p>
            <p>Space Complexity: ${space}</p>
            <p>${message}</p>
        </div>
        <div class="modal-footer">
            <button class="close-btn">Close</button>
        </div>
    `;

        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        overlay.querySelector(".close-btn").addEventListener("click", () => {
            overlay.remove();
        });

        overlay.addEventListener("click", (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    }

    function createButton() {
        const button = document.createElement('button');

        button.id = 'complexity-btn';
        button.textContent = 'Complexity';

        button.style.padding = '0px 10px';
        button.style.marginLeft = '8px';
        button.style.border = 'none';
        button.style.borderRadius = '6px';
        button.style.cursor = 'pointer';
        button.style.fontWeight = '600';
        button.style.backgroundColor = '#222222';
        button.style.color = '#26AA3E';
        button.style.fontSize = '14px';

        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#2F2F2F';
        });

        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = '#222222';
        });

        return button;
    }

    function addButton() {
        const toolbar = document.getElementById('ide-top-btns');

        if (!toolbar) return false;

        if (document.getElementById('complexity-btn')) return true;

        const customButton = createButton();

        customButton.addEventListener('click', analyzeComplexity);

        toolbar.appendChild(customButton);

        return true;
    }

    function getCode() {
        try {
            const monacoCode =
                  window.monaco?.editor?.getModels?.()[0]?.getValue();

            if (monacoCode?.trim()) {
                return monacoCode;
            }

            const viewLines =
                  document.querySelector('.view-lines')?.innerText;

            if (viewLines?.trim()) {
                return viewLines;
            }

            return null;
        } catch (error) {
            console.error(error);
            return null;
        }
    }

    async function analyzeComplexity() {
        try {
            const button = document.getElementById('complexity-btn');

            button.disabled = true;
            button.textContent = 'Analyzing...';

            const code = getCode();

            if (!code) {
                throw new Error('Could not extract code from editor.');
            }

            const response = await fetch('https://leetcode-complexity-analyzer.onrender.com', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    code
                })
            });

            if (!response.ok) {
                const err = await response.text();
                throw new Error(err);
            }

            const result = await response.json();

            createDialog(result.timeComplexity, result.spaceComplexity, result.explanation);

            // alert(
            //     `Time Complexity: ${result.timeComplexity}\n\n` +
            //     `Space Complexity: ${result.spaceComplexity}\n\n` +
            //     `Explanation: ${result.explanation}`
            // );
        } catch (error) {
            console.error(error);
            alert(`Error:\n\n${error.message}`);
        } finally {
            const button = document.getElementById('complexity-btn');

            if (button) {
                button.disabled = false;
                button.textContent = 'Complexity';
            }
        }
    }

    const style = document.createElement("style");
    style.textContent = `
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 9999;
    color: white;
}

.modal {
color: white;
    background: #222222;
    width: 400px;
    max-width: 90%;
    border-radius: 12px;
    padding: 20px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.2);
    animation: fadeIn 0.2s ease;
}

.modal-header h2 {
    margin: 0 0 10px;
    color: white;
}

.modal-body {
    margin-bottom: 20px;
    color: white;
}

.modal-body p {
    margin: 10px;
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
}

.close-btn {
    padding: 8px 16px;
    border: none;
    background: #2563eb;
    color: white;
    border-radius: 6px;
    cursor: pointer;
}

.close-btn:hover {
    background: #1d4ed8;
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: scale(0.95);
    }
    to {
        opacity: 1;
        transform: scale(1);
    }
}
`;
    document.head.appendChild(style);

    addButton();

















    // QUES PER DAY

    let activeDaysCache = null;

    async function getLifetimeActiveDays() {
        if (activeDaysCache !== null) {
            return activeDaysCache;
        }

        const username = location.pathname.split('/')[2];

        async function fetchYear(year) {
            const res = await fetch('https://leetcode.com/graphql/', {
                method: 'POST',
                credentials: 'include',
                headers: {
                    'content-type': 'application/json'
                },
                body: JSON.stringify({
                    operationName: 'userProfileCalendar',
                    variables: {
                        username,
                        year
                    },
                    query: `
                query userProfileCalendar($username: String!, $year: Int) {
                  matchedUser(username: $username) {
                    userCalendar(year: $year) {
                      activeYears
                      submissionCalendar
                    }
                  }
                }
                `
                })
            });

            return res.json();
        }

        const currentYear = new Date().getFullYear();

        const first = await fetchYear(currentYear);

        const years =
              first.data.matchedUser.userCalendar.activeYears || [];

        const activeDays = new Set();

        const responses = await Promise.all(
            years.map(year => fetchYear(year))
        );

        for (const data of responses) {
            const calendar = JSON.parse(
                data.data.matchedUser.userCalendar.submissionCalendar || "{}"
            );

            Object.keys(calendar).forEach(day => {
                activeDays.add(day);
            });
        }

        activeDaysCache = activeDays.size;

        return activeDaysCache;
    }

    function createD(ques, days) {
        const d = document.createElement('div');

        d.id = 'questions-per-day';
        d.className = 'mr-4.5 space-x-1';

        d.innerHTML = `
        <span class="text-label-3 dark:text-dark-label-3">
            Avg Solves/Day:
        </span>
        <span class="font-medium text-label-2 dark:text-dark-label-2">
            ${days > 0 ? (ques / days).toFixed(2) : '0.00'}
        </span>
    `;

        return d;
    }

    let qpdRendering = false;

    async function addD() {
        if (!location.pathname.startsWith('/u/')) {
            return false;
        }

        if (document.getElementById('questions-per-day')) {
            return true;
        }

        if(qpdRendering) return false;

        qpdRendering = true;

        try {
            const activeDaysLabel = [...document.querySelectorAll('span')]
            .find(el => el.textContent.includes('Total active days'));

            if (!activeDaysLabel) {
                return false;
            }

            const statsBar =
                  activeDaysLabel.closest('div.flex.items-center.text-xs') ||
                  activeDaysLabel.parentElement?.parentElement;

            if (!statsBar) {
                return false;
            }

            const solvedElement = [...document.querySelectorAll('span')]
            .find(el =>
                  /^\d+$/.test(el.textContent.trim()) &&
                  el.parentElement?.textContent.includes('/')
                 );

            if (!solvedElement) {
                return false;
            }

            const solved = parseInt(
                solvedElement.textContent.trim(),
                10
            );

            // create placeholder immediately
            const placeholder = createD(0, 1);

            placeholder.querySelectorAll('span')[1].textContent = '...';

            statsBar.prepend(placeholder);

            // fetch in background
            const activeDays = await getLifetimeActiveDays();

            if (!activeDays || activeDays <= 0) {
                placeholder.querySelectorAll('span')[1].textContent = 'N/A';
                return false;
            }

            // update placeholder
            placeholder.querySelectorAll('span')[1].textContent =
                (solved / activeDays).toFixed(2);

            return true;
        } finally {
            qpdRendering = false;
        }
    }


    const observer = new MutationObserver(async () => {
        if(SETTINGS.questionsPerDay) addD();
        if(SETTINGS.complexityButton) addButton();
        if(SETTINGS.autocomplete) enableAutocomplete();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();