您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enjoy your study
// ==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(); } })();