uCertify Hero

自动通过在线搜索和ChatGPT 4o回答uCertify上的问题

目前为 2024-06-15 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name uCertify Hero
  3. // @name:zh-CN uCertify Hero
  4. // @name:zh-TW uCertify Hero
  5. // @namespace http://tampermonkey.net/
  6. // @version 1.8
  7. // @description Automates answering questions on uCertify using online searches and ChatGPT 4o
  8. // @description:zh-CN 自动通过在线搜索和ChatGPT 4o回答uCertify上的问题
  9. // @description:zh-TW 自動通過線上搜尋和ChatGPT 4o回答uCertify上的問題
  10. // @author TFG
  11. // @match https://www.ucertify.com/app/*
  12. // @grant GM_registerMenuCommand
  13. // @grant GM_xmlhttpRequest
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. // Debounce function to limit the rate at which the main function is called
  20. function debounce(func, wait) {
  21. let timeout;
  22. return function(...args) {
  23. const later = () => {
  24. clearTimeout(timeout);
  25. func(...args);
  26. };
  27. clearTimeout(timeout);
  28. timeout = setTimeout(later, wait);
  29. };
  30. }
  31.  
  32. // Function to create a small loading spinner element
  33. function createSmallSpinner() {
  34. const spinner = document.createElement('div');
  35. spinner.className = 'quiz-helper-small-spinner';
  36. spinner.style.border = '4px solid #f3f3f3';
  37. spinner.style.borderTop = '4px solid #3498db';
  38. spinner.style.borderRadius = '50%';
  39. spinner.style.width = '24px';
  40. spinner.style.height = '24px';
  41. spinner.style.animation = 'spin 1s linear infinite';
  42.  
  43. const style = document.createElement('style');
  44. style.type = 'text/css';
  45. style.innerHTML = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
  46. document.getElementsByTagName('head')[0].appendChild(style);
  47.  
  48. return spinner;
  49. }
  50.  
  51. // Function to show the spinner inside the answer area
  52. function showSmallSpinner() {
  53. let answerElement = document.querySelector('.quiz-helper-answer');
  54. if (!answerElement) {
  55. answerElement = document.createElement('div');
  56. answerElement.className = 'quiz-helper-answer';
  57. const questionElement = document.querySelector('.test-question ._title') || document.querySelector('.test-question [data-itemtype="question"]');
  58. if (questionElement) {
  59. questionElement.appendChild(answerElement);
  60. }
  61. }
  62. const spinner = createSmallSpinner();
  63. answerElement.innerHTML = ''; // Clear any previous content
  64. answerElement.appendChild(spinner);
  65. }
  66.  
  67. // Function to hide the spinner
  68. function hideSmallSpinner() {
  69. const spinner = document.querySelector('.quiz-helper-small-spinner');
  70. if (spinner) {
  71. spinner.remove();
  72. }
  73. }
  74.  
  75.  
  76. // Function to get the quiz title from the webpage
  77. function getQuizTitle() {
  78. const titleElement = document.querySelector('a.nav-link.text-body.text-truncate');
  79. return titleElement ? titleElement.innerText.trim() : 'Quiz';
  80. }
  81.  
  82. // Function to get the question and options from the webpage
  83. function getQuestionAndOptions() {
  84. const questionElement = document.querySelector('.test-question ._title') || document.querySelector('.test-question [data-itemtype="question"]');
  85. const question = questionElement ? questionElement.innerText.trim() : '';
  86. console.log('Question:', question); // Debug output
  87.  
  88. let options = [];
  89. const optionElementsLeft = document.querySelectorAll('.shuffleList1 .matchlist_list');
  90. const optionElementsRight = document.querySelectorAll('.shuffleList2 .matchlist_list');
  91.  
  92. if (optionElementsLeft.length > 0 && optionElementsRight.length > 0) {
  93. options = {
  94. left: Array.from(optionElementsLeft).map(option => option.innerText.trim()),
  95. right: Array.from(optionElementsRight).map(option => option.innerText.trim())
  96. };
  97. console.log('Matching options:', options); // Debug output
  98. } else {
  99. const optionsElements = document.querySelectorAll('#item_answer .radio_label, #item_answer .chekcbox_label');
  100. options = Array.from(optionsElements).map(option => option.innerText.trim().replace(/^\w\./, '').trim());
  101. console.log('Multiple choice options:', options); // Debug output
  102. }
  103.  
  104. return { question, options };
  105. }
  106.  
  107. // Function to perform DuckDuckGo search using GM_xmlhttpRequest
  108. async function duckDuckGoSearch(query) {
  109. const url = `https://duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
  110. return new Promise((resolve, reject) => {
  111. GM_xmlhttpRequest({
  112. method: 'GET',
  113. url: url,
  114. onload: function(response) {
  115. if (response.status === 200) {
  116. const parser = new DOMParser();
  117. const doc = parser.parseFromString(response.responseText, 'text/html');
  118.  
  119. const results = Array.from(doc.querySelectorAll('.result')).slice(0, 5).map(result => ({
  120. title: result.querySelector('.result__a')?.innerText,
  121. snippet: result.querySelector('.result__snippet')?.innerText,
  122. link: result.querySelector('.result__a')?.href
  123. }));
  124.  
  125. resolve(results);
  126. } else {
  127. reject('Error fetching search results');
  128. }
  129. },
  130. onerror: function() {
  131. reject('Network error');
  132. }
  133. });
  134. });
  135. }
  136.  
  137. // Function to get search suggestions from GPT
  138. async function getSearchSuggestions(title, question, options) {
  139. const apiKey = localStorage.getItem('openai_api_key');
  140. if (!apiKey) {
  141. alert('API Key not set. Please go to the settings menu to configure your API Key.');
  142. return 'API Key not set';
  143. }
  144.  
  145. let prompt;
  146. if (options.left && options.right) {
  147. 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.`;
  148. } else if (options.length > 0) {
  149. 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.`;
  150. }
  151.  
  152. console.log('Prompt for search suggestions:', prompt);
  153.  
  154. try {
  155. const response = await fetch('https://api.openai.com/v1/chat/completions', {
  156. method: 'POST',
  157. headers: {
  158. 'Content-Type': 'application/json',
  159. 'Authorization': `Bearer ${apiKey}`
  160. },
  161. body: JSON.stringify({
  162. model: 'gpt-4o',
  163. messages: [
  164. {"role": "system", "content": "You are a helpful assistant."},
  165. {"role": "user", "content": prompt}
  166. ]
  167. })
  168. });
  169. const data = await response.json();
  170. console.log('Search suggestions API Response:', data);
  171.  
  172. if (data.choices && data.choices.length > 0) {
  173. return data.choices[0].message.content.trim();
  174. } else {
  175. console.error('No search suggestions found in API response');
  176. return 'No search suggestions found';
  177. }
  178. } catch (error) {
  179. console.error('Error fetching search suggestions:', error);
  180. return 'Error fetching search suggestions';
  181. }
  182. }
  183.  
  184. // Function to get answer from GPT with search results
  185. async function getChatGPTAnswerWithSearchResults(title, question, options, searchResults) {
  186. const apiKey = localStorage.getItem('openai_api_key');
  187. if (!apiKey) {
  188. alert('API Key not set. Please go to the settings menu to configure your API Key.');
  189. return 'API Key not set';
  190. }
  191.  
  192. let prompt;
  193. if (options.left && options.right) {
  194. 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')}\nSearch Results:\n${searchResults.map(result => `${result.title}\n${result.snippet}`).join('\n\n')}\nPlease provide only the correct matches in the format "1-A\\n2-B\\n3-C". Do not include any additional text.`;
  195. } else if (options.length > 0) {
  196. prompt = `Quiz 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')}\nPlease provide only the letter(s) of the correct answer(s) (e.g., A, B, C, or D) without any explanation.`;
  197. }
  198.  
  199. console.log('Prompt for final answer:', prompt);
  200.  
  201. try {
  202. const response = await fetch('https://api.openai.com/v1/chat/completions', {
  203. method: 'POST',
  204. headers: {
  205. 'Content-Type': 'application/json',
  206. 'Authorization': `Bearer ${apiKey}`
  207. },
  208. body: JSON.stringify({
  209. model: 'gpt-4o',
  210. messages: [
  211. {"role": "system", "content": "You are a helpful assistant."},
  212. {"role": "user", "content": prompt}
  213. ],
  214. max_tokens:1000
  215. })
  216. });
  217. const data = await response.json();
  218. console.log('Final answer API Response:', data);
  219.  
  220. if (data.choices && data.choices.length > 0) {
  221. return data.choices[0].message.content.trim();
  222. } else {
  223. console.error('No choices found in API response');
  224. return 'No answer found';
  225. }
  226. } catch (error) {
  227. console.error('Error fetching final answer:', error);
  228. return 'Error fetching final answer';
  229. }
  230. }
  231.  
  232. // Function to display the answer on the webpage
  233. function displayAnswer(answer) {
  234. // Check if the answer is already displayed
  235. const answerElement = document.querySelector('.quiz-helper-answer');
  236.  
  237. if (!answerElement) {
  238. const newAnswerElement = document.createElement('div');
  239. }
  240. const newAnswerElement = document.createElement('div');
  241. newAnswerElement.className = 'quiz-helper-answer';
  242. newAnswerElement.innerHTML = answer;
  243. newAnswerElement.style.color = 'red';
  244. newAnswerElement.style.fontWeight = 'bold';
  245.  
  246. const questionElement = document.querySelector('.test-question ._title') || document.querySelector('.test-question [data-itemtype="question"]');
  247. if (questionElement) {
  248. questionElement.appendChild(newAnswerElement);
  249. }
  250.  
  251. // Hide the spinner once the answer is displayed
  252. hideSmallSpinner();
  253. }
  254.  
  255. // Main function to get question and options, and then display the answer
  256. async function main() {
  257. // Check if the answer is already displayed before fetching it
  258. if (!document.querySelector('.quiz-helper-answer')) {
  259.  
  260. const title = getQuizTitle();
  261. const { question, options } = getQuestionAndOptions();
  262.  
  263. if (question && ((options.left && options.left.length > 0) || options.length > 0)) {
  264. // Show spinner
  265. showSmallSpinner();
  266.  
  267. const searchSuggestions = await getSearchSuggestions(title, question, options);
  268. console.log('Search Suggestions:', searchSuggestions);
  269.  
  270. const searchResults = await duckDuckGoSearch(searchSuggestions);
  271. console.log('Search Results:', searchResults);
  272.  
  273. const answer = await getChatGPTAnswerWithSearchResults(title, question, options, searchResults);
  274. displayAnswer(answer);
  275. }
  276.  
  277. // Hide spinner (in case answer already displayed)
  278. hideSmallSpinner();
  279. } else {
  280. console.log('No question or options found');
  281. }
  282. }
  283.  
  284. // Call the createSmallSpinner function once to initialize the spinner element
  285. createSmallSpinner();
  286.  
  287. // Function to set API keys
  288. function setApiKeys() {
  289. const openaiApiKey = prompt('Enter your OpenAI API key:');
  290. if (openaiApiKey) {
  291. localStorage.setItem('openai_api_key', openaiApiKey);
  292. }
  293. alert('API key saved successfully!');
  294. }
  295.  
  296. // Register menu command to set API keys
  297. GM_registerMenuCommand('Set API Keys', setApiKeys);
  298.  
  299. // Observe changes in the DOM and rerun main function with debounce
  300. const debouncedMain = debounce(main, 500);
  301. const observer = new MutationObserver((mutations) => {
  302. mutations.forEach((mutation) => {
  303. if (mutation.addedNodes.length || mutation.removedNodes.length) {
  304. console.log('DOM changed, running main function');
  305. debouncedMain();
  306. }
  307. });
  308. });
  309.  
  310. // Start observing the entire document for changes
  311. observer.observe(document.body, { childList: true, subtree: true });
  312.  
  313. // Run the main function after the page has fully loaded
  314. window.addEventListener('load', main);
  315. })();