uCertify Helper

Enjoy your study

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         uCertify Helper
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Enjoy your study
// @match        *.ucertify.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let enableAutoCard = false;
    let enableMarkAsRead = false;

    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    function createSmallSpinner() {
        const spinner = document.createElement('div');
        spinner.className = 'quiz-helper-small-spinner';
        spinner.style.border = '4px solid #f3f3f3';
        spinner.style.borderTop = '4px solid #3498db';
        spinner.style.borderRadius = '50%';
        spinner.style.width = '24px';
        spinner.style.height = '24px';
        spinner.style.animation = 'spin 1s linear infinite';

        const style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
        document.getElementsByTagName('head')[0].appendChild(style);

        return spinner;
    }

    function showSmallSpinner() {
        let answerElement = document.querySelector('.quiz-helper-answer');
        if (!answerElement) {
            answerElement = document.createElement('div');
            answerElement.className = 'quiz-helper-answer';
            const questionElement = document.querySelector('.test-question ._title') || document.querySelector('.test-question [data-itemtype="question"]');
            if (questionElement) {
                questionElement.appendChild(answerElement);
            }
        }
        const spinner = createSmallSpinner();
        answerElement.innerHTML = '';
        answerElement.appendChild(spinner);
    }

    function hideSmallSpinner() {
        const spinner = document.querySelector('.quiz-helper-small-spinner');
        if (spinner) {
            spinner.remove();
        }
    }

    function enableAndClickButton() {
        const correctButton = document.getElementById('correct');
        if (correctButton && correctButton.disabled) {
            correctButton.disabled = false;
            correctButton.click();
        }
    }

    function startAutoCard() {
        const interval = setInterval(() => {
            if (enableAutoCard) {
                enableAndClickButton();
            }
        }, 500);
    }

    function clickMarkAsRead() {
        if (!enableMarkAsRead) return;

        const readingIndicators = document.querySelectorAll('.dropdown.reset_reading_time.pointer > div[data-bs-toggle="dropdown"]');
        readingIndicators.forEach(indicator => {
            if (!indicator.classList.contains('show')) {
                indicator.click();
            }
            const markAsReadLink = indicator.nextElementSibling?.querySelector('.dropdown-item.mark_read');
            if (markAsReadLink) {
                markAsReadLink.click();
            }
        });
    }

    function startMarkAsRead() {
        window.addEventListener('load', clickMarkAsRead);

        (function() {
            let originalXHR = window.XMLHttpRequest;
            window.XMLHttpRequest = function() {
                let xhr = new originalXHR();
                let originalSend = xhr.send;
                xhr.send = function() {
                    this.addEventListener('load', function() {
                        clickMarkAsRead();
                    });
                    originalSend.apply(xhr, arguments);
                };
                return xhr;
            };
        })();

        setInterval(clickMarkAsRead, 500);
    }

    function getQuizTitle() {
        const titleElement = document.querySelector('a.nav-link.text-body.text-truncate');
        return titleElement ? titleElement.innerText.trim() : 'Quiz';
    }

    function getQuestionAndOptions() {
        const questionElement = document.querySelector('.test-question ._title') || document.querySelector('.test-question [data-itemtype="question"]');
        const question = questionElement ? questionElement.innerText.trim() : '';
        console.log('Question:', question); // Debug output

        let options = [];
        const optionElementsLeft = document.querySelectorAll('.shuffleList1 .matchlist_list');
        const optionElementsRight = document.querySelectorAll('.shuffleList2 .matchlist_list');

        if (optionElementsLeft.length > 0 && optionElementsRight.length > 0) {
            options = {
                left: Array.from(optionElementsLeft).map(option => option.innerText.trim()),
                right: Array.from(optionElementsRight).map(option => option.innerText.trim())
            };
            console.log('Matching options:', options); // Debug output
        } else {
            const optionsElements = document.querySelectorAll('#item_answer .radio_label, #item_answer .chekcbox_label');
            options = Array.from(optionsElements).map(option => option.innerText.trim().replace(/^\w\./, '').trim());
            console.log('Multiple choice options:', options); // Debug output
        }

        return { question, options };
    }

    // Function to perform DuckDuckGo search using GM_xmlhttpRequest
    async function duckDuckGoSearch(query) {
        const url = `https://duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onload: function(response) {
                    if (response.status === 200) {
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(response.responseText, 'text/html');

                        const results = Array.from(doc.querySelectorAll('.result')).slice(0, 5).map(result => ({
                            title: result.querySelector('.result__a')?.innerText,
                            snippet: result.querySelector('.result__snippet')?.innerText,
                            link: result.querySelector('.result__a')?.href
                        }));

                        resolve(results);
                    } else {
                        console.error('Error fetching search results:', response);
                        reject('Error fetching search results');
                    }
                },
                onerror: function(error) {
                    console.error('Network error:', error);
                    reject('Network error');
                }
            });
        });
    }

    // Function to get search suggestions from GPT
    async function getSearchSuggestions(title, question, options) {
        const apiUrl = GM_getValue('openai_api_url');
        const apiKey = GM_getValue('openai_api_key');
        const apimodel = GM_getValue('openai_api_model');

        let prompt;
        if (options.left && options.right) {
            prompt = `Quiz Title: ${title}\nQuestion: ${question}\nMatch the following terms to their definitions:\nTerms:\n${options.left.join('\n')}\nDefinitions:\n${options.right.join('\n')}\nPlease provide only the search keywords.`;
        } else if (options.length > 0) {
            prompt = `Quiz Title: ${title}\nQuestion: ${question}\nOptions:\n${options.map((opt, index) => String.fromCharCode(65 + index) + '. ' + opt).join('\n')}\nPlease provide only the search keywords.`;
        }

        console.log('Prompt for search suggestions:', prompt);

        try {
            const response = await fetch(apiUrl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${apiKey}`
                },
                body: JSON.stringify({
                    model: apimodel,
                    messages: [
                        {"role": "system", "content": "You are a helpful assistant."},
                        {"role": "user", "content": prompt}
                    ]
                })
            });
            const data = await response.json();
            console.log('Search suggestions API Response:', data);

            if (data.choices && data.choices.length > 0) {
                return data.choices[0].message.content.trim();
            } else {
                console.error('No search suggestions found in API response');
                return 'No search suggestions found';
            }
        } catch (error) {
            console.error('Error fetching search suggestions:', error);
            return 'Error fetching search suggestions';
        }
    }

    // Function to get answer from GPT with search results
    async function getChatGPTAnswerWithSearchResults(title, question, options, searchResults) {
        const apiUrl = GM_getValue('openai_api_url');
        const apiKey = GM_getValue('openai_api_key');
        const apimodel = GM_getValue('openai_api_model');

        let prompt;
        if (options.left && options.right) {
            prompt = `Please provide only the correct matches in the format "1-A\\n2-B\\n3-C". Do not include any additional text.\n\nQuiz Title: ${title}\nQuestion: ${question}\nMatch the following terms to their definitions:\nTerms:\n${options.left.join('\n')}\nDefinitions:\n${options.right.join('\n')}\nSearch Results:\n${searchResults.map(result => `${result.title}\n${result.snippet}`).join('\n\n')}`;
        } else if (options.length > 0) {
            prompt = `Please provide only the letter(s) of the correct answer(s) (e.g., A, B, C, or D) without any explanation.\n\nQuiz Title: ${title}\nQuestion: ${question}\nOptions:\n${options.map((opt, index) => String.fromCharCode(65 + index) + '. ' + opt).join('\n')}\nSearch Results:\n${searchResults.map(result => `${result.title}\n${result.snippet}`).join('\n\n')}`;
        }

        console.log('Prompt for final answer:', prompt);

        try {
            const response = await fetch(apiUrl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${apiKey}`
                },
                body: JSON.stringify({
                    model: apimodel,
                    messages: [
                        {"role": "system", "content": "You are a helpful assistant."},
                        {"role": "user", "content": prompt}
                    ],
                    max_tokens: 1000
                })
            });
            const data = await response.json();
            console.log('Final answer API Response:', data);

            if (data.choices && data.choices.length > 0) {
                return data.choices[0].message.content.trim();
            } else {
                console.error('No choices found in API response');
                return 'No answer found';
            }
        } catch (error) {
            console.error('Error fetching final answer:', error);
            return 'Error fetching final answer';
        }
    }

    function displayAnswer(answer) {
        const answerElement = document.querySelector('.quiz-helper-answer');

        if (!answerElement) {
            const newAnswerElement = document.createElement('div');
        }
        const newAnswerElement = document.createElement('div');
        newAnswerElement.className = 'quiz-helper-answer';
        newAnswerElement.innerHTML = answer;
        newAnswerElement.style.color = 'red';
        newAnswerElement.style.fontWeight = 'bold';

        const questionElement = document.querySelector('.test-question ._title') || document.querySelector('.test-question [data-itemtype="question"]');
        if (questionElement) {
            questionElement.appendChild(newAnswerElement);
        }

        hideSmallSpinner();
    }

    async function answerMain() {
        if (!document.querySelector('.quiz-helper-answer')) {

            const title = getQuizTitle();
            const { question, options } = getQuestionAndOptions();

            if (question && ((options.left && options.left.length > 0) || options.length > 0)) {
                showSmallSpinner();

                const searchSuggestions = await getSearchSuggestions(title, question, options);
                console.log('Search Suggestions:', searchSuggestions);

                const searchResults = await duckDuckGoSearch(searchSuggestions);
                console.log('Search Results:', searchResults);

                const answer = await getChatGPTAnswerWithSearchResults(title, question, options, searchResults);
                displayAnswer(answer);
            }

            hideSmallSpinner();
        } else {
            console.log('No question or options found');
        }
    }

    function createControlPanel() {
        const panel = document.createElement('div');
        panel.style.position = 'fixed';
        panel.style.top = '10px';
        panel.style.right = '10px';
        panel.style.width = '300px';
        panel.style.height = '300px';
        panel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        panel.style.color = 'white';
        panel.style.padding = '10px';
        panel.style.borderRadius = '5px';
        panel.style.zIndex = '10000';
        panel.style.fontSize = '14px';
        panel.style.display = 'none'; // Initially hidden

        const autoCardToggle = document.createElement('label');
        autoCardToggle.innerHTML = `Auto Card: <input type="checkbox" ${enableAutoCard ? 'checked' : ''}>`;
        autoCardToggle.style.display = 'block';
        autoCardToggle.style.marginBottom = '5px';
        autoCardToggle.querySelector('input').addEventListener('change', (e) => {
            enableAutoCard = e.target.checked;
        });

        const markAsReadToggle = document.createElement('label');
        markAsReadToggle.innerHTML = `Mark as Read: <input type="checkbox" ${enableMarkAsRead ? 'checked' : ''}>`;
        markAsReadToggle.style.display = 'block';
        markAsReadToggle.style.marginBottom = '5px';
        markAsReadToggle.querySelector('input').addEventListener('change', (e) => {
            enableMarkAsRead = e.target.checked;
            if (enableMarkAsRead) {
                startMarkAsRead();
            }
        });

        const apiURLInput = document.createElement('input');
        apiURLInput.type = 'text';
        apiURLInput.placeholder = 'OpenAI API URL';
        apiURLInput.style.width = '100%';
        apiURLInput.style.marginBottom = '5px';
        apiURLInput.value = GM_getValue('openai_api_url', 'https://api.openai.com/v1/chat/completions');

        const apiKeyInput = document.createElement('input');
        apiKeyInput.type = 'text';
        apiKeyInput.placeholder = 'OpenAI API Key';
        apiKeyInput.style.width = '100%';
        apiKeyInput.style.marginBottom = '5px';
        apiKeyInput.value = GM_getValue('openai_api_key', '');

        const apiModelSelect = document.createElement('select');
        apiModelSelect.style.width = '100%';
        apiModelSelect.style.marginBottom = '10px';
        const models = ['', 'gpt-4o', 'gpt-4o-mini', 'gpt-4o-all', 'gpt-4-turbo', 'claude-3-5-sonnet-latest', 'gemini-2.0-flash-thinking-exp', 'gemini-2.0-flash-exp', 'gemini-1.5-flash'];
        models.forEach(model => {
            const option = document.createElement('option');
            option.value = model;
            option.textContent = model || 'Select a model';
            if (model === GM_getValue('openai_api_model')) {
                option.selected = true;
            }
            apiModelSelect.appendChild(option);
        });

        const saveButton = document.createElement('button');
        saveButton.textContent = 'Save Settings';
        saveButton.style.marginTop = '10px';
        saveButton.style.backgroundColor = 'green';
        saveButton.style.color = 'white';
        saveButton.style.border = 'none';
        saveButton.style.padding = '5px 10px';
        saveButton.style.cursor = 'pointer';
        saveButton.addEventListener('click', () => {
            GM_setValue('openai_api_url', apiURLInput.value);
            GM_setValue('openai_api_key', apiKeyInput.value);
            GM_setValue('openai_api_model', apiModelSelect.value);
            alert('OpenAI settings saved!');
        });

        panel.appendChild(autoCardToggle);
        panel.appendChild(markAsReadToggle);
        panel.appendChild(apiURLInput);
        panel.appendChild(apiKeyInput);
        panel.appendChild(apiModelSelect);
        panel.appendChild(saveButton);

        document.body.appendChild(panel);

        const dragIcon = document.createElement('div');
        dragIcon.style.position = 'fixed';
        dragIcon.style.top = '10px';
        dragIcon.style.right = '10px';
        dragIcon.style.width = '40px';
        dragIcon.style.height = '40px';
        dragIcon.style.backgroundImage = 'url(https://rcdn.tonyha7.com/sliver__wolf_playing.jpg)';
        dragIcon.style.backgroundSize = 'cover';
        dragIcon.style.backgroundPosition = 'center';
        dragIcon.style.borderRadius = '50%';
        dragIcon.style.cursor = 'pointer';
        dragIcon.style.opacity = '0.5';
        dragIcon.style.zIndex = '10001';

        dragIcon.addEventListener('mouseover', () => {
            dragIcon.style.opacity = '1';
        });

        dragIcon.addEventListener('mouseout', () => {
            dragIcon.style.opacity = '0.5';
        });

        dragIcon.addEventListener('click', () => {
            panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
        });

        let isDragging = false;
        let offsetX, offsetY;

        dragIcon.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.offsetX;
            offsetY = e.offsetY;
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const x = e.clientX - offsetX;
                const y = e.clientY - offsetY;
                dragIcon.style.top = `${y}px`;
                dragIcon.style.left = `${x}px`;
                panel.style.top = `${y}px`;
                panel.style.left = `${x + 50}px`;
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
        });

        document.body.appendChild(dragIcon);
    }

    // Call the createSmallSpinner function once to initialize the spinner element
    createSmallSpinner();

    // Observe changes in the DOM and rerun main function with debounce
    const debouncedMain = debounce(answerMain, 500);
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length || mutation.removedNodes.length) {
                console.log('DOM changed, running main function');
                debouncedMain();
            }
        });
    });

    // Start observing the entire document for changes
    observer.observe(document.body, { childList: true, subtree: true });

    // Init
    if (window.location.href.includes('get_flash_card')) {
        startAutoCard();
    }

    if (window.location.href.includes('func=ebook') && enableMarkAsRead) {
        startMarkAsRead();
    }

    if (window.location.href.includes('app')) {
        window.addEventListener('load', answerMain);
    }

    if (window.location.href.includes('ucertify.com')) {
        createControlPanel();
    }
})();