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
    });
})();