Albert.io Question Scraper

Scrape Albert.io questions and answer choices, very basic

// ==UserScript==
// @name         Albert.io Question Scraper
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Scrape Albert.io questions and answer choices, very basic
// @author       You
// @match        https://*.albert.io/*
// @grant        GM_addStyle
// @license     GPL-3.0-or-later
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        .albert-dl-btn {
            background-color: #4080BD;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 5px 10px;
            margin: 5px;
            cursor: pointer;
            font-size: 12px;
            transition: background-color 0.3s;
            z-index: 9999;
        }
        .albert-dl-btn:hover {
            background-color: #336699;
        }
        .albert-primary-btn {
            background-color: #4080BD;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 8px 15px;
            margin: 5px;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
            transition: background-color 0.3s;
        }
        .albert-primary-btn:hover {
            background-color: #336699;
        }
        .albert-danger-btn {
            background-color: #dc3545;
            color: white;
        }
        .albert-danger-btn:hover {
            background-color: #bd2130;
        }
        .albert-success-btn {
            background-color: #28a745;
            color: white;
        }
        .albert-success-btn:hover {
            background-color: #218838;
        }
        .albert-dl-all-btn {
            position: fixed;
            top: 10px;
            right: 10px;
            background-color: #4080BD;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 8px 12px;
            font-size: 14px;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        }
        .albert-scraper-panel {
            position: fixed;
            top: 50px;
            right: 10px;
            background-color: white;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 15px;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            display: none;
            max-width: 400px;
            font-family: Arial, sans-serif;
        }
        .albert-question-count {
            margin-bottom: 10px;
            font-weight: bold;
        }
        .albert-option-row {
            margin-bottom: 10px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
        }
        .albert-option-label {
            flex-grow: 1;
            margin-right: 10px;
            font-size: 14px;
        }
        .albert-checkbox-container {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }
        .albert-checkbox {
            margin-right: 8px;
        }
        .albert-progress {
            margin-top: 10px;
            background-color: #f0f0f0;
            border-radius: 4px;
            height: 20px;
            overflow: hidden;
            display: none;
        }
        .albert-progress-bar {
            background-color: #4080BD;
            height: 100%;
            width: 0%;
            transition: width 0.3s;
        }
        .albert-progress-text {
            text-align: center;
            margin-top: 5px;
            font-size: 12px;
        }
        .albert-question-checkbox {
            position: absolute;
            top: 5px;
            right: 5px;
            z-index: 9999;
        }
        .albert-checkbox-label {
            font-size: 12px;
            cursor: pointer;
        }
        .albert-tab-container {
            margin-bottom: 15px;
        }
        .albert-tab {
            display: inline-block;
            padding: 8px 15px;
            background-color: #f0f0f0;
            border: 1px solid #ccc;
            border-bottom: none;
            border-radius: 4px 4px 0 0;
            cursor: pointer;
        }
        .albert-tab.active {
            background-color: #4080BD;
            color: white;
        }
        .albert-tab-content {
            border: 1px solid #ccc;
            padding: 15px;
            border-radius: 0 4px 4px 4px;
        }
        .albert-input-group {
            margin-bottom: 10px;
            display: flex;
            align-items: center;
        }
        .albert-input-label {
            flex-grow: 1;
            margin-right: 10px;
        }
        .albert-input {
            width: 70px;
            padding: 5px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        .albert-switch {
            position: relative;
            display: inline-block;
            width: 45px;
            height: 24px;
        }
        .albert-switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }
        .albert-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            transition: .4s;
            border-radius: 24px;
        }
        .albert-slider:before {
            position: absolute;
            content: "";
            height: 18px;
            width: 18px;
            left: 3px;
            bottom: 3px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }
        input:checked + .albert-slider {
            background-color: #4080BD;
        }
        input:checked + .albert-slider:before {
            transform: translateX(21px);
        }
        .albert-status-badge {
            display: inline-block;
            padding: 3px 8px;
            border-radius: 10px;
            font-size: 12px;
            font-weight: bold;
            margin-left: 5px;
        }
        .albert-status-badge.active {
            background-color: #28a745;
            color: white;
        }
        .albert-status-badge.inactive {
            background-color: #dc3545;
            color: white;
        }
        .albert-status-badge.pending {
            background-color: #ffc107;
            color: black;
        }
        .albert-divider {
            margin: 15px 0;
            border-top: 1px solid #eee;
        }
        .albert-section-title {
            font-weight: bold;
            margin-bottom: 10px;
            font-size: 16px;
        }
        .albert-counter {
            font-weight: bold;
            color: #4080BD;
        }
        .albert-textarea {
            width: 100%;
            height: 150px;
            margin-top: 10px;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-family: monospace;
            font-size: 12px;
            resize: vertical;
        }
        .albert-button-container {
            display: flex;
            justify-content: center;
            margin-top: 10px;
        }
        .albert-debug {
            position: fixed;
            bottom: 10px;
            left: 10px;
            padding: 8px;
            background-color: rgba(0,0,0,0.7);
            color: white;
            font-size: 10px;
            border-radius: 4px;
            z-index: 9999;
        }
    `);

    let foundQuestions = [];
    let selectedQuestions = [];
    let autoNavigationActive = false;
    let autoNavigationDelay = 2000;
    let questionsProcessed = 0;
    let totalQuestionsToProcess = 0;
    let collectedContent = '';
    let currentQuestionNumber = 1;
    let debugMode = false;

    window.addEventListener('load', function() {
        setTimeout(initScraper, 2000);
    });

    function initScraper() {
        console.log('Albert.io Question Scraper is running...');

        findAndProcessQuestions();

        addScanButton();

        observeDOMChanges();
    }

    function findAndProcessQuestions() {

        foundQuestions = [];

        const questions = document.querySelectorAll('.question-wrapper, .question-container, .question');

        if (questions && questions.length > 0) {
            console.log(`Found ${questions.length} question(s) with standard selectors`);
            processQuestions(Array.from(questions));
        } else {
            console.log('No questions found with standard selectors. Trying alternative approach...');
            scanForQuestions();
        }
    }

    function processQuestions(questions) {

        foundQuestions = questions;

        if (questions.length > 0) {
            addScraperPanel(questions);
        }

        questions.forEach((question, index) => {
            addSelectionToQuestion(question, index);
            addDownloadButton(question, index);
        });
    }

    function addSelectionToQuestion(questionElement, index) {

        if (questionElement.querySelector('.albert-question-checkbox')) {
            return;
        }

        const checkboxContainer = document.createElement('div');
        checkboxContainer.className = 'albert-question-checkbox';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = `albert-question-${index}`;
        checkbox.className = 'albert-checkbox';
        checkbox.dataset.index = index;
        checkbox.addEventListener('change', function() {
            if (this.checked) {
                if (!selectedQuestions.includes(index)) {
                    selectedQuestions.push(index);
                }
            } else {
                const selectedIndex = selectedQuestions.indexOf(index);
                if (selectedIndex > -1) {
                    selectedQuestions.splice(selectedIndex, 1);
                }
            }
            updateSelectedCount();
        });

        const label = document.createElement('label');
        label.className = 'albert-checkbox-label';
        label.htmlFor = `albert-question-${index}`;
        label.textContent = 'Select';

        checkboxContainer.appendChild(checkbox);
        checkboxContainer.appendChild(label);

        const insertTarget = questionElement.querySelector('.question-wrapper__heading') ||
                            questionElement.querySelector('.mcq-option') ||
                            questionElement;

        insertTarget.style.position = 'relative';
        insertTarget.appendChild(checkboxContainer);
    }

    function downloadTextAsFile(filename, text) {
        const blob = new Blob([text], {type: 'text/plain'});
        const url = URL.createObjectURL(blob);

        const element = document.createElement('a');
        element.href = url;
        element.download = filename;
        element.style.display = 'none';
        document.body.appendChild(element);
        element.click();

        setTimeout(function() {
            document.body.removeChild(element);
            URL.revokeObjectURL(url);
        }, 100);
    }

    function extractQuestionContent(questionElement = null) {
        let questionText = '';
        let questionTitle = '';
        let questionNumber = currentQuestionNumber;
        let optionsFound = false;

        if (!questionElement) {

            questionElement = document.querySelector('.practice-view__question-area') ||
                             document.querySelector('.question-wrapper') ||
                             document.querySelector('.question-container') ||
                             document.querySelector('.question') ||
                             document.body;
        }

        if (debugMode) {
            logDebug(`Extracting from ${questionElement.className}`);
        }

        const titleElem = document.querySelector('.student-practice-view-toolbar__title');
        if (titleElem) {
            questionTitle = titleElem.textContent.trim();
            questionText += "Title: " + questionTitle + "\n\n";
        }

        const questionNumElem = document.querySelector('[data-testid="question-dropdown-navigator__toggle-button"]');
        if (questionNumElem) {
            const questionNumText = questionNumElem.textContent.trim();
            const match = questionNumText.match(/Question\s+(\d+)/i);
            if (match && match[1]) {
                questionNumber = parseInt(match[1]);
                questionText += "Question " + questionNumber + "\n\n";
            } else {
                questionText += "Question " + questionNumber + "\n\n";
            }
        } else {
            questionText += "Question " + questionNumber + "\n\n";
        }

        const instructions = document.querySelector('.question-wrapper__body, .instructions-pane');
        if (instructions) {
            const markdownContent = instructions.querySelector('.markdown-renderer-v2');
            if (markdownContent) {
                questionText += markdownContent.textContent.trim() + "\n\n";
            } else {
                questionText += instructions.textContent.trim() + "\n\n";
            }
        }

        let options = document.querySelectorAll('.mcq-option-eliminator');
        if (options && options.length > 0) {
            questionText += "Options:\n";
            optionsFound = true;

            options.forEach((option) => {
                const letterElement = option.querySelector('.mcq-option__letter');
                const contentElement = option.querySelector('.mcq-option__content');

                if (letterElement && contentElement) {
                    const letter = letterElement.textContent.trim();
                    const content = contentElement.textContent.trim();
                    questionText += letter + ") " + content + "\n";
                }
            });

            questionText += "\n";

            if (debugMode) {
                logDebug(`Found ${options.length} options with method 1`);
            }
        }

        if (!optionsFound) {
            options = document.querySelectorAll('.mcq-option-accessible-wrapper');
            if (options && options.length > 0) {
                questionText += "Options:\n";
                optionsFound = true;

                options.forEach((option) => {
                    const letterElement = option.querySelector('.mcq-option__letter');
                    const contentElement = option.querySelector('.mcq-option__content');

                    if (letterElement && contentElement) {
                        const letter = letterElement.textContent.trim();
                        const content = contentElement.textContent.trim();
                        questionText += letter + ") " + content + "\n";
                    }
                });

                questionText += "\n";

                if (debugMode) {
                    logDebug(`Found ${options.length} options with method 2`);
                }
            }
        }

        if (!optionsFound) {
            options = document.querySelectorAll('label[for^="input-"]');
            if (options && options.length > 0) {
                questionText += "Options:\n";
                optionsFound = true;

                options.forEach((option, index) => {
                    const letterElement = option.querySelector('.mcq-option__letter');
                    const contentElement = option.querySelector('.mcq-option__content');

                    const letter = letterElement ? letterElement.textContent.trim() : String.fromCharCode(65 + index);

                    const content = contentElement
                        ? contentElement.textContent.trim()
                        : option.textContent.replace(letter, '').trim();

                    questionText += letter + ") " + content + "\n";
                });

                questionText += "\n";

                if (debugMode) {
                    logDebug(`Found ${options.length} options with method 3`);
                }
            }
        }

        if (!optionsFound) {
            const selector = '[class*="option" i]:not(.albert-option-row):not(.albert-option-label)';
            options = document.querySelectorAll(selector);
            if (options && options.length > 0) {
                questionText += "Options:\n";
                optionsFound = true;

                options.forEach((option, index) => {

                    const letter = String.fromCharCode(65 + index);

                    let content = option.textContent.trim();

                    content = content.replace(/^[A-E][)\]]?\s*/, '');

                    questionText += letter + ") " + content + "\n";
                });

                questionText += "\n";

                if (debugMode) {
                    logDebug(`Found ${options.length} options with method 4`);
                }
            }
        }

        if (debugMode) {
            logDebug(`Options found: ${optionsFound}`);
        }

        const explanation = document.querySelector('.explanation-content, .explanation');
        if (explanation) {
            questionText += "Explanation:\n" + explanation.textContent.trim() + "\n";
        }

        const tables = document.querySelectorAll('table');
        if (tables && tables.length > 0) {
            tables.forEach((table, idx) => {
                questionText += `\nTable ${idx + 1}:\n`;

                const headers = table.querySelectorAll('th');
                if (headers.length > 0) {
                    const headerRow = Array.from(headers).map(th => th.textContent.trim());
                    questionText += headerRow.join(' | ') + '\n';
                    questionText += '-'.repeat(headerRow.join(' | ').length) + '\n';
                }

                const rows = table.querySelectorAll('tr');
                rows.forEach(row => {

                    if (row.querySelector('th')) return;

                    const cells = row.querySelectorAll('td');
                    if (cells.length > 0) {
                        questionText += Array.from(cells).map(td => td.textContent.trim()).join(' | ') + '\n';
                    }
                });

                questionText += '\n';
            });
        }

        return {
            text: questionText,
            number: questionNumber,
            optionsFound: optionsFound
        };
    }

    function addDownloadButton(questionElement, index) {

        if (questionElement.querySelector('.albert-dl-btn')) {
            return;
        }

        const downloadBtn = document.createElement('button');
        downloadBtn.className = 'albert-dl-btn';
        downloadBtn.innerText = 'Save as TXT';
        downloadBtn.addEventListener('click', function(e) {
            e.preventDefault();
            e.stopPropagation();

            downloadCurrentQuestion(questionElement);
        });

        const insertTarget = questionElement.querySelector('.question-wrapper__heading') ||
                             questionElement.querySelector('.mcq-option') ||
                             questionElement;

        insertTarget.style.position = 'relative';
        insertTarget.appendChild(downloadBtn);
    }

    function downloadCurrentQuestion(questionElement) {
        const extract = extractQuestionContent(questionElement);
        const content = extract.text;
        let filename = `albert_question_${extract.number}.txt`;

        const contentLines = content.split('\n');
        if (contentLines.length > 0 && contentLines[0].startsWith('Title:')) {
            const title = contentLines[0].replace('Title:', '').trim();
            filename = `albert_${title.substring(0, 30).replace(/[^a-z0-9]/gi, '_')}_q${extract.number}.txt`;
        }

        if (content.trim() !== '') {
            downloadTextAsFile(filename, content);

            if (autoNavigationActive) {
                questionsProcessed++;
                updateAutoNavigationStatus();
            }
        } else {
            alert('Could not extract question content. Please try using the "Scan for Questions" button.');
        }
    }

    function addScraperPanel(questions) {

        if (document.querySelector('.albert-scraper-panel')) {
            updateScraperPanel(questions);
            return;
        }

        const panel = document.createElement('div');
        panel.className = 'albert-scraper-panel';

        const tabContainer = document.createElement('div');
        tabContainer.className = 'albert-tab-container';

        const autoNavTab = document.createElement('div');
        autoNavTab.className = 'albert-tab active';
        autoNavTab.textContent = 'Auto Collector';
        autoNavTab.dataset.tab = 'autonav';

        const manualTab = document.createElement('div');
        manualTab.className = 'albert-tab';
        manualTab.textContent = 'Manual Selection';
        manualTab.dataset.tab = 'manual';

        tabContainer.appendChild(autoNavTab);
        tabContainer.appendChild(manualTab);
        panel.appendChild(tabContainer);

        const tabContent = document.createElement('div');
        tabContent.className = 'albert-tab-content';
        panel.appendChild(tabContent);

        const autoNavContent = document.createElement('div');
        autoNavContent.id = 'albert-autonav-tab';

        const autoNavDescription = document.createElement('p');
        autoNavDescription.innerText = 'Automatically navigate through all questions, collecting them into a single file.';
        autoNavContent.appendChild(autoNavDescription);

        const delayGroup = document.createElement('div');
        delayGroup.className = 'albert-input-group';

        const delayLabel = document.createElement('label');
        delayLabel.className = 'albert-input-label';
        delayLabel.innerText = 'Delay between questions (ms)';

        const delayInput = document.createElement('input');
        delayInput.type = 'number';
        delayInput.min = '1000';
        delayInput.max = '10000';
        delayInput.step = '500';
        delayInput.value = autoNavigationDelay;
        delayInput.className = 'albert-input';
        delayInput.addEventListener('change', function() {
            const value = parseInt(this.value);
            if (value >= 1000 && value <= 10000) {
                autoNavigationDelay = value;
            } else {
                this.value = autoNavigationDelay;
                alert('Please enter a value between 1000 and 10000 milliseconds');
            }
        });

        delayGroup.appendChild(delayLabel);
        delayGroup.appendChild(delayInput);

        autoNavContent.appendChild(delayGroup);

        const debugGroup = document.createElement('div');
        debugGroup.className = 'albert-input-group';

        const debugLabel = document.createElement('label');
        debugLabel.className = 'albert-input-label';
        debugLabel.innerText = 'Debug Mode';

        const debugSwitch = document.createElement('label');
        debugSwitch.className = 'albert-switch';

        const debugCheckbox = document.createElement('input');
        debugCheckbox.type = 'checkbox';
        debugCheckbox.checked = debugMode;
        debugCheckbox.addEventListener('change', function() {
            debugMode = this.checked;
            if (debugMode) {
                addDebugPanel();
            } else {
                removeDebugPanel();
            }
        });

        const debugSlider = document.createElement('span');
        debugSlider.className = 'albert-slider';

        debugSwitch.appendChild(debugCheckbox);
        debugSwitch.appendChild(debugSlider);

        debugGroup.appendChild(debugLabel);
        debugGroup.appendChild(debugSwitch);

        autoNavContent.appendChild(debugGroup);

        const statusGroup = document.createElement('div');
        statusGroup.className = 'albert-input-group';

        const statusLabel = document.createElement('label');
        statusLabel.className = 'albert-input-label';
        statusLabel.innerText = 'Auto-Collection Status';

        const statusBadge = document.createElement('span');
        statusBadge.className = 'albert-status-badge inactive';
        statusBadge.id = 'albert-autonav-status';
        statusBadge.innerText = 'Inactive';

        statusGroup.appendChild(statusLabel);
        statusGroup.appendChild(statusBadge);

        autoNavContent.appendChild(statusGroup);

        const counterGroup = document.createElement('div');
        counterGroup.className = 'albert-input-group';

        const counterLabel = document.createElement('label');
        counterLabel.className = 'albert-input-label';
        counterLabel.innerText = 'Questions Collected';

        const counterValue = document.createElement('span');
        counterValue.className = 'albert-counter';
        counterValue.id = 'albert-questions-processed';
        counterValue.innerText = '0';

        counterGroup.appendChild(counterLabel);
        counterGroup.appendChild(counterValue);

        autoNavContent.appendChild(counterGroup);

        const divider = document.createElement('div');
        divider.className = 'albert-divider';
        autoNavContent.appendChild(divider);

        const textareaLabel = document.createElement('div');
        textareaLabel.className = 'albert-section-title';
        textareaLabel.innerText = 'Collected Content';
        autoNavContent.appendChild(textareaLabel);

        const contentTextarea = document.createElement('textarea');
        contentTextarea.className = 'albert-textarea';
        contentTextarea.id = 'albert-collected-content';
        contentTextarea.readOnly = true;
        contentTextarea.placeholder = 'Collected question content will appear here...';
        contentTextarea.value = collectedContent;
        autoNavContent.appendChild(contentTextarea);

        const downloadCollectedBtn = document.createElement('button');
        downloadCollectedBtn.className = 'albert-primary-btn';
        downloadCollectedBtn.innerText = 'Download Collected Content';
        downloadCollectedBtn.id = 'albert-download-collected';
        downloadCollectedBtn.addEventListener('click', function() {
            downloadCollectedContent();
        });
        downloadCollectedBtn.style.width = '100%';
        downloadCollectedBtn.style.marginTop = '10px';

        autoNavContent.appendChild(downloadCollectedBtn);

        const navBtnGroup = document.createElement('div');
        navBtnGroup.className = 'albert-button-container';

        const startNavBtn = document.createElement('button');
        startNavBtn.className = 'albert-primary-btn albert-success-btn';
        startNavBtn.innerText = 'Start Auto-Collection';
        startNavBtn.id = 'albert-start-autonav';
        startNavBtn.addEventListener('click', function() {
            startAutoNavigation();
        });

        const stopNavBtn = document.createElement('button');
        stopNavBtn.className = 'albert-primary-btn albert-danger-btn';
        stopNavBtn.innerText = 'Stop';
        stopNavBtn.id = 'albert-stop-autonav';
        stopNavBtn.style.display = 'none';
        stopNavBtn.addEventListener('click', function() {
            stopAutoNavigation();
        });

        navBtnGroup.appendChild(startNavBtn);
        navBtnGroup.appendChild(stopNavBtn);

        autoNavContent.appendChild(navBtnGroup);

        tabContent.appendChild(autoNavContent);

        const manualContent = document.createElement('div');
        manualContent.id = 'albert-manual-tab';
        manualContent.style.display = 'none';

        const countDisplay = document.createElement('div');
        countDisplay.className = 'albert-question-count';
        countDisplay.innerText = `Found ${questions.length} question(s)`;
        countDisplay.id = 'albert-question-count';
        manualContent.appendChild(countDisplay);

        const selectedDisplay = document.createElement('div');
        selectedDisplay.className = 'albert-question-count';
        selectedDisplay.style.fontSize = '12px';
        selectedDisplay.innerText = `Selected: 0 question(s)`;
        selectedDisplay.id = 'albert-selected-count';
        manualContent.appendChild(selectedDisplay);

        const selectionOptions = document.createElement('div');
        selectionOptions.className = 'albert-option-row';

        const selectAllBtn = document.createElement('button');
        selectAllBtn.className = 'albert-dl-btn';
        selectAllBtn.innerText = 'Select All';
        selectAllBtn.addEventListener('click', function() {
            selectedQuestions = [];
            for (let i = 0; i < questions.length; i++) {
                selectedQuestions.push(i);
                const checkbox = document.getElementById(`albert-question-${i}`);
                if (checkbox) {
                    checkbox.checked = true;
                }
            }
            updateSelectedCount();
        });

        const deselectAllBtn = document.createElement('button');
        deselectAllBtn.className = 'albert-dl-btn';
        deselectAllBtn.innerText = 'Deselect All';
        deselectAllBtn.addEventListener('click', function() {
            selectedQuestions = [];
            for (let i = 0; i < questions.length; i++) {
                const checkbox = document.getElementById(`albert-question-${i}`);
                if (checkbox) {
                    checkbox.checked = false;
                }
            }
            updateSelectedCount();
        });

        selectionOptions.appendChild(selectAllBtn);
        selectionOptions.appendChild(deselectAllBtn);
        manualContent.appendChild(selectionOptions);

        const downloadAllBtn = document.createElement('button');
        downloadAllBtn.className = 'albert-dl-btn';
        downloadAllBtn.style.width = '100%';
        downloadAllBtn.style.marginTop = '10px';
        downloadAllBtn.innerText = 'Download All as One File';
        downloadAllBtn.addEventListener('click', function() {
            downloadAllQuestionsAsOne(questions);
        });

        manualContent.appendChild(downloadAllBtn);

        const downloadSelectedBtn = document.createElement('button');
        downloadSelectedBtn.className = 'albert-dl-btn';
        downloadSelectedBtn.style.width = '100%';
        downloadSelectedBtn.style.marginTop = '10px';
        downloadSelectedBtn.innerText = 'Download Selected as One File';
        downloadSelectedBtn.addEventListener('click', function() {
            if (selectedQuestions.length === 0) {
                alert('Please select at least one question to download.');
                return;
            }
            downloadSelectedQuestionsAsOne(questions);
        });

        manualContent.appendChild(downloadSelectedBtn);

        tabContent.appendChild(manualContent);

        const closeBtn = document.createElement('button');
        closeBtn.className = 'albert-dl-btn';
        closeBtn.innerText = 'Close';
        closeBtn.style.marginTop = '15px';
        closeBtn.addEventListener('click', function() {
            panel.style.display = 'none';
            toggleButton.style.display = 'block';
        });
        tabContent.appendChild(closeBtn);

        document.body.appendChild(panel);

        const toggleButton = document.createElement('button');
        toggleButton.className = 'albert-dl-all-btn';
        toggleButton.innerText = 'Albert.io Scraper';
        toggleButton.addEventListener('click', function() {
            panel.style.display = 'block';
            toggleButton.style.display = 'none';
        });

        document.body.appendChild(toggleButton);

        autoNavTab.addEventListener('click', function() {
            setActiveTab('autonav');
        });

        manualTab.addEventListener('click', function() {
            setActiveTab('manual');
        });
    }

    function addDebugPanel() {
        if (document.getElementById('albert-debug-panel')) {
            return;
        }

        const debugPanel = document.createElement('div');
        debugPanel.id = 'albert-debug-panel';
        debugPanel.className = 'albert-debug';
        debugPanel.textContent = 'Debug mode active';
        document.body.appendChild(debugPanel);
    }

    function removeDebugPanel() {
        const debugPanel = document.getElementById('albert-debug-panel');
        if (debugPanel) {
            debugPanel.remove();
        }
    }

    function logDebug(message) {
        console.log('[Albert.io Scraper] ' + message);

        const debugPanel = document.getElementById('albert-debug-panel');
        if (debugPanel) {
            debugPanel.textContent = message;
        }
    }

    function startAutoNavigation() {
        if (autoNavigationActive) {
            return;
        }

        questionsProcessed = 0;
        collectedContent = '';

        const contentTextarea = document.getElementById('albert-collected-content');
        if (contentTextarea) {
            contentTextarea.value = '';
        }

        const questionNavbar = document.querySelector('[data-testid="question-dropdown-navigator__toggle-button"]');
        if (questionNavbar) {
            const navbarText = questionNavbar.textContent.trim();
            const match = navbarText.match(/Question\s+\d+\s+\/\s+(\d+)/i);
            if (match && match[1]) {
                totalQuestionsToProcess = parseInt(match[1]);
            }
        }

        if (!totalQuestionsToProcess) {
            totalQuestionsToProcess = 100;
        }

        document.getElementById('albert-autonav-status').innerText = 'Active';
        document.getElementById('albert-autonav-status').className = 'albert-status-badge active';
        document.getElementById('albert-start-autonav').style.display = 'none';
        document.getElementById('albert-stop-autonav').style.display = 'block';

        autoNavigationActive = true;

        processCurrentQuestion();
    }

    function stopAutoNavigation() {
        autoNavigationActive = false;

        document.getElementById('albert-autonav-status').innerText = 'Stopped';
        document.getElementById('albert-autonav-status').className = 'albert-status-badge inactive';
        document.getElementById('albert-start-autonav').style.display = 'block';
        document.getElementById('albert-stop-autonav').style.display = 'none';
    }

    function processCurrentQuestion() {
        if (!autoNavigationActive) {
            return;
        }

        setTimeout(() => {

            const extract = extractQuestionContent();

            if (extract.text.trim() !== '') {

                collectedContent += `==================== QUESTION ${extract.number} ====================\n\n`;
                collectedContent += extract.text;
                collectedContent += '\n\n';

                const contentTextarea = document.getElementById('albert-collected-content');
                if (contentTextarea) {
                    contentTextarea.value = collectedContent;
                    contentTextarea.scrollTop = contentTextarea.scrollHeight;
                }

                questionsProcessed++;
                updateAutoNavigationStatus();

                if (debugMode) {
                    logDebug(`Question ${extract.number}: Options found = ${extract.optionsFound}`);
                }
            } else {
                if (debugMode) {
                    logDebug('No content extracted for current question');
                }
            }

            setTimeout(navigateToNextQuestion, autoNavigationDelay);
        }, 1000);
    }

    function navigateToNextQuestion() {
        if (!autoNavigationActive) {
            return;
        }

        const nextButton = document.querySelector('[data-testid="question-dropdown-navigator__next-button"]');

        if (nextButton && !nextButton.disabled) {

            nextButton.click();

            setTimeout(processCurrentQuestion, 1000);
        } else {

            console.log('Reached the end of the questions');
            stopAutoNavigation();
            alert(`Auto-collection complete! Collected ${questionsProcessed} questions. You can now download the content.`);
        }
    }

    function downloadCollectedContent() {
        if (collectedContent.trim() === '') {
            alert('No content has been collected yet. Please start auto-collection first.');
            return;
        }

        let assignmentName = document.querySelector('.student-practice-view-toolbar__title')?.textContent.trim() || 'questions';
        assignmentName = assignmentName.substring(0, 30).replace(/[^a-z0-9]/gi, '_');

        const timeStamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 16);

        downloadTextAsFile(`albert_${assignmentName}_all_${questionsProcessed}_questions_${timeStamp}.txt`, collectedContent);
    }

    function updateAutoNavigationStatus() {
        const counterElement = document.getElementById('albert-questions-processed');
        if (counterElement) {
            counterElement.innerText = `${questionsProcessed} / ${totalQuestionsToProcess}`;
        }
    }

    function updateScraperPanel(questions) {

        const countDisplay = document.getElementById('albert-question-count');
        if (countDisplay) {
            countDisplay.innerText = `Found ${questions.length} question(s)`;
        }

        const contentTextarea = document.getElementById('albert-collected-content');
        if (contentTextarea) {
            contentTextarea.value = collectedContent;
        }

        updateSelectedCount();
    }

    function setActiveTab(tabName) {

        const tabs = document.querySelectorAll('.albert-tab');
        tabs.forEach(tab => {
            if (tab.dataset.tab === tabName) {
                tab.classList.add('active');
            } else {
                tab.classList.remove('active');
            }
        });

        const autoNavContent = document.getElementById('albert-autonav-tab');
        const manualContent = document.getElementById('albert-manual-tab');

        autoNavContent.style.display = tabName === 'autonav' ? 'block' : 'none';
        manualContent.style.display = tabName === 'manual' ? 'block' : 'none';
    }

    function updateSelectedCount() {
        const selectedDisplay = document.getElementById('albert-selected-count');
        if (selectedDisplay) {
            selectedDisplay.innerText = `Selected: ${selectedQuestions.length} question(s)`;
        }
    }

    function downloadAllQuestionsAsOne(questions) {
        let allContent = '';
        let validQuestions = 0;

        for (let i = 0; i < questions.length; i++) {
            const question = questions[i];
            const extract = extractQuestionContent(question);

            if (extract.text.trim() !== '') {
                validQuestions++;
                allContent += `==================== QUESTION ${extract.number} ====================\n\n`;
                allContent += extract.text;
                allContent += '\n\n';
            }
        }

        if (allContent.trim() !== '') {

            let assignmentName = document.querySelector('.student-practice-view-toolbar__title')?.textContent.trim() || 'questions';
            assignmentName = assignmentName.substring(0, 30).replace(/[^a-z0-9]/gi, '_');

            const timeStamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 16);

            downloadTextAsFile(`albert_${assignmentName}_all_${validQuestions}_questions_${timeStamp}.txt`, allContent);
        } else {
            alert('Could not extract any question content.');
        }
    }

    function downloadSelectedQuestionsAsOne(questions) {
        if (selectedQuestions.length === 0) {
            alert('Please select at least one question to download.');
            return;
        }

        let allContent = '';
        let validQuestions = 0;

        for (const index of selectedQuestions) {
            if (index >= 0 && index < questions.length) {
                const question = questions[index];
                const extract = extractQuestionContent(question);

                if (extract.text.trim() !== '') {
                    validQuestions++;
                    allContent += `==================== QUESTION ${extract.number} ====================\n\n`;
                    allContent += extract.text;
                    allContent += '\n\n';
                }
            }
        }

        if (allContent.trim() !== '') {

            let assignmentName = document.querySelector('.student-practice-view-toolbar__title')?.textContent.trim() || 'questions';
            assignmentName = assignmentName.substring(0, 30).replace(/[^a-z0-9]/gi, '_');

            const timeStamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 16);

            downloadTextAsFile(`albert_${assignmentName}_selected_${validQuestions}_questions_${timeStamp}.txt`, allContent);
        } else {
            alert('Could not extract any question content from selected questions.');
        }
    }

    function scanForQuestions() {

        const potentialQuestions = [];

        const albertElements = document.querySelectorAll('.markdown-renderer-v2, .question-statement, .mcq-option');
        if (albertElements.length > 0) {

            let commonParents = [];
            albertElements.forEach(el => {

                let parent = el;
                for (let i = 0; i < 3; i++) {
                    if (parent.parentElement) parent = parent.parentElement;
                }
                if (parent && !commonParents.includes(parent)) {
                    commonParents.push(parent);
                }
            });

            commonParents.forEach(parent => {
                if (!potentialQuestions.includes(parent)) {
                    potentialQuestions.push(parent);
                }
            });
        }

        const allElements = document.querySelectorAll('div:not([class*="albert-"]), section, article');
        allElements.forEach(element => {
            const text = element.innerText;

            if (
                (text.includes('Question') && text.length > 100) ||
                /[A-E]\)\s/.test(text) ||
                (element.querySelector('table') && text.includes('Instructions')) ||
                (text.match(/p-value|t-test|confidence interval/i) && text.length > 200)
            ) {

                let isContained = false;
                for (const potentialQuestion of potentialQuestions) {
                    if (potentialQuestion.contains(element) && potentialQuestion !== element) {
                        isContained = true;
                        break;
                    }
                }

                if (!isContained && element.offsetHeight > 100) {
                    potentialQuestions.push(element);
                }
            }
        });

        if (potentialQuestions.length > 0) {
            console.log(`Found ${potentialQuestions.length} potential question elements`);
            processQuestions(potentialQuestions);
        } else {
            console.log('No potential questions found');

            const message = document.createElement('div');
            message.style.position = 'fixed';
            message.style.top = '10px';
            message.style.left = '10px';
            message.style.padding = '10px';
            message.style.backgroundColor = '#f8d7da';
            message.style.color = '#721c24';
            message.style.borderRadius = '4px';
            message.style.zIndex = '9999';
            message.innerHTML = 'No questions detected. Try using the "Scan for Questions" button manually.';
            document.body.appendChild(message);

            setTimeout(() => {
                message.remove();
            }, 5000);
        }
    }

    function addScanButton() {
        const scanBtn = document.createElement('button');
        scanBtn.className = 'albert-dl-btn';
        scanBtn.innerText = 'Scan for Questions';
        scanBtn.style.position = 'fixed';
        scanBtn.style.bottom = '10px';
        scanBtn.style.left = '10px';
        scanBtn.style.zIndex = '9999';
        scanBtn.addEventListener('click', function(e) {
            e.preventDefault();
            scanForQuestions();
        });
        document.body.appendChild(scanBtn);
    }

    function observeDOMChanges() {
        const observer = new MutationObserver(function(mutations) {
            let shouldRescan = false;

            mutations.forEach(function(mutation) {
                if (mutation.addedNodes && mutation.addedNodes.length > 0) {

                    for (let i = 0; i < mutation.addedNodes.length; i++) {
                        const node = mutation.addedNodes[i];
                        if (node.nodeType === 1) {
                            if (
                                (node.classList && (
                                    node.classList.contains('question-wrapper') ||
                                    node.classList.contains('question-container') ||
                                    node.classList.contains('question')
                                )) ||
                                node.querySelector('.question-wrapper, .question-container, .question, .mcq-option')
                            ) {
                                shouldRescan = true;
                                break;
                            }
                        }
                    }
                }
            });

            if (shouldRescan) {
                console.log('New question content detected, rescanning...');

                setTimeout(findAndProcessQuestions, 1000);
            }
        });

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

QingJ © 2025

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