您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A unified tool for cheating on online test sites
// ==UserScript== // @name Universal Educational Tool Suite // @namespace http://tampermonkey.net/ // @version 1.3.2 // @description A unified tool for cheating on online test sites // @author Nyx // @license GPL-3.0 // @match https://quizizz.com/* // @match https://wayground.com/* // @match https://*.quizizz.com/* // @match https://*.wayground.com/* // @match https://*.testportal.net/* // @match https://*.testportal.pl/* // @match https://docs.google.com/forms/* // @match *://kahoot.it/* // @grant GM_addStyle // @grant GM_log // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_setClipboard // @connect * // ==/UserScript== (function () { "use strict"; // === SHARED CONSTANTS === const GEMINI_API_KEY_STORAGE = "UETS_GEMINI_API_KEY"; const UI_MODS_ENABLED_KEY = "uets_ui_modifications_enabled"; const CONFIG_STORAGE_KEY = "UETS_CONFIG"; const DEFAULT_CONFIG = { enableTimeTakenEdit: true, timeTakenMin: 5067, timeTakenMax: 7067, enableTimerHijack: true, timerBonusPoints: 270, enableSpoofFullscreen: true, serverUrl: "https://uets.fuckingbitch.eu", geminiApiKey: "", geminiModel: "gemini-2.5-flash", thinkingBudget: 256, maxOutputTokens: 1024, temperature: 0.2, topP: 0.95, topK: 64, includeImages: true, enableReactionSpam: true, reactionSpamCount: 1, reactionSpamDelay: 2000 }; const PROFILES = { "True Stealth": { enableTimeTakenEdit: false, enableTimerHijack: false, enableSpoofFullscreen: true, enableReactionSpam: false, }, "Stealthy Extended": { enableTimeTakenEdit: true, timeTakenMin: 8000, timeTakenMax: 14000, enableTimerHijack: true, timerBonusPoints: 200, enableSpoofFullscreen: true, enableReactionSpam: false, }, "Creator's choice": { enableTimeTakenEdit: true, timeTakenMin: 6000, timeTakenMax: 8000, enableTimerHijack: true, timerBonusPoints: 270, enableSpoofFullscreen: true, enableReactionSpam: false, }, "LMAO": { enableTimeTakenEdit: true, timeTakenMin: 1000, timeTakenMax: 2000, enableTimerHijack: true, timerBonusPoints: 5000, enableSpoofFullscreen: true, enableReactionSpam: true, reactionSpamCount: 2, reactionSpamDelay: 500 }, }; // === SHARED STATE === const sharedState = { uiModificationsEnabled: GM_getValue(UI_MODS_ENABLED_KEY, true), toggleButton: null, geminiPopup: null, elementsToCleanup: [], observer: null, currentDomain: window.location.hostname, originalRegExpTest: RegExp.prototype.test, quizData: {}, currentQuestionId: null, questionsPool: {}, config: GM_getValue(CONFIG_STORAGE_KEY, DEFAULT_CONFIG), configGui: null, holdTimeout: null, originalTabLeaveHTML: null, originalStartButtonText: null, firstRunKey: "UETS_FIRST_RUN", kahootSocket: null, kahootClientId: null, kahootGameId: null, kahootCurrentQuestion: null, kahootAnswerCounts: {}, kahootHasConnected: false }; // === SHARED STYLES === GM_addStyle(` @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/icon?family=Material+Icons+Outlined'); :root { --md-primary: #6750A4; --md-primary-container: #EADDFF; --md-on-primary: #FFFFFF; --md-on-primary-container: #21005D; --md-secondary: #625B71; --md-secondary-container: #E8DEF8; --md-on-secondary: #FFFFFF; --md-on-secondary-container: #1D192B; --md-tertiary: #7D5260; --md-tertiary-container: #FFD8E4; --md-on-tertiary: #FFFFFF; --md-on-tertiary-container: #31111D; --md-surface: #FEF7FF; --md-surface-dim: #DED8E1; --md-surface-bright: #FEF7FF; --md-surface-container-lowest: #FFFFFF; --md-surface-container-low: #F7F2FA; --md-surface-container: #F1ECF4; --md-surface-container-high: #ECE6F0; --md-surface-container-highest: #E6E0E9; --md-on-surface: #1C1B1F; --md-on-surface-variant: #49454F; --md-outline: #79747E; --md-outline-variant: #CAC4D0; --md-error: #B3261E; --md-error-container: #F9DEDC; --md-on-error: #FFFFFF; --md-on-error-container: #410E0B; --md-shadow: #000000; } .uets-card { background: var(--md-surface-container); border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); overflow: hidden; font-family: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif; } .uets-elevated-card { background: var(--md-surface-container-low); border-radius: 12px; box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); overflow: hidden; font-family: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif; } .uets-filled-button { background: var(--md-primary); color: var(--md-on-primary); border: none; border-radius: 20px; padding: 10px 24px; font-family: 'Roboto', sans-serif; font-weight: 500; font-size: 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); text-decoration: none; min-height: 40px; justify-content: center; } .uets-filled-button:hover { box-shadow: 0 2px 4px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); transform: translateY(-1px); } .uets-filled-button:active { transform: translateY(0px); box-shadow: 0 1px 2px rgba(0,0,0,0.12); } .uets-outlined-button { background: transparent; color: var(--md-primary); border: 1px solid var(--md-outline); border-radius: 20px; padding: 10px 24px; font-family: 'Roboto', sans-serif; font-weight: 500; font-size: 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); text-decoration: none; min-height: 40px; justify-content: center; } .uets-outlined-button:hover { background: rgba(103, 80, 164, 0.08); border-color: var(--md-primary); } .uets-text-button { background: transparent; color: var(--md-primary); border: none; border-radius: 20px; padding: 10px 12px; font-family: 'Roboto', sans-serif; font-weight: 500; font-size: 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); text-decoration: none; min-height: 40px; justify-content: center; } .uets-text-button:hover { background: rgba(103, 80, 164, 0.08); } .uets-fab { background: var(--md-primary-container); color: var(--md-on-primary-container); border: none; border-radius: 16px; width: 56px; height: 56px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 3px 5px rgba(0,0,0,0.2), 0 6px 10px rgba(0,0,0,0.14), 0 1px 18px rgba(0,0,0,0.12); transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); font-size: 24px; } .uets-fab:hover { box-shadow: 0 5px 5px rgba(0,0,0,0.2), 0 9px 18px rgba(0,0,0,0.14), 0 3px 14px rgba(0,0,0,0.12); transform: scale(1.05); } .uets-fab.uets-mods-hidden-state { background: transparent; box-shadow: none; } .uets-fab.uets-mods-hidden-state:hover { background: rgba(103, 80, 164, 0.08); box-shadow: none; transform: scale(1.05); } .uets-success-button { background: #a6e3a1; color: white; } .uets-warning-button { background: #fab387; color: white; } .uets-purple-button { background: #cba6f7; color: white; } .uets-ddg-link, .uets-gemini-button, .uets-copy-prompt-button, .uets-ai-button, .uets-ddg-button, .uets-get-answer-button { display: inline-flex; align-items: center; gap: 8px; padding: 4px 8px; color: var(--md-on-primary); text-decoration: none; border-radius: 20px; font-size: 14px; font-weight: 500; cursor: pointer; text-align: center; vertical-align: middle; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); border: none; font-family: 'Roboto', sans-serif; min-height: 40px; margin: 1px; justify-content: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06); } .uets-ddg-link:hover, .uets-gemini-button:hover, .uets-copy-prompt-button:hover, .uets-ai-button:hover, .uets-ddg-button:hover, .uets-get-answer-button:hover { box-shadow: 0 4px 8px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.1); transform: translateY(-2px); } .uets-ddg-link, .uets-ddg-button { background: #a6e3a1 !important; color: white !important; } .uets-gemini-button, .uets-ai-button { background: #74c7ec !important; color: white !important; } .uets-copy-prompt-button { background: #fab387 !important; color: white !important; } .uets-get-answer-button { background: #cba6f7 !important; color: white !important; } .uets-ddg-link::before, .uets-ddg-button::before { content: 'search'; font-family: 'Material Icons Outlined'; font-size: 18px; } .uets-gemini-button::before, .uets-ai-button::before { content: 'psychology'; font-family: 'Material Icons Outlined'; font-size: 18px; } .uets-copy-prompt-button::before { content: 'content_copy'; font-family: 'Material Icons Outlined'; font-size: 18px; } .uets-get-answer-button::before { content: 'lightbulb'; font-family: 'Material Icons Outlined'; font-size: 18px; } .uets-option-wrapper { display: flex; flex-direction: column; align-items: stretch; justify-content: space-between; height: 100%; } .uets-option-wrapper > button.option { display: flex; flex-direction: column; flex-grow: 1; min-height: 0; width: 100%; } .uets-ddg-link-option-item { width: 100%; box-sizing: border-box; margin-top: 12px; padding: 8px 0; border-radius: 0 0 12px 12px; flex-shrink: 0; } .uets-main-question-buttons-container { display: flex; justify-content: center; gap: 4px; background: #313244; border-radius: 12px; margin: 1px; flex-wrap: wrap; padding: 2px; } .uets-response-popup { position: fixed; top: 20%; left: 50%; transform: translate(-50%, -50%); background: var(--md-surface-container-high); color: var(--md-on-surface); border-radius: 28px; padding: 0; z-index: 10004; min-width: 320px; max-width: 90vh; max-height: 80vh; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.35), 0 6px 10px rgba(0,0,0,0.25); font-family: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 14px; } .uets-welcome-popup { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--md-surface-container-high); color: var(--md-on-surface); border-radius: 28px; padding: 0; z-index: 10004; min-width: 320px; max-width: 90vh; max-height: 80vh; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.35), 0 6px 10px rgba(0,0,0,0.25); font-family: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif !important; font-size: 18px; } .uets-response-popup-header { display: flex; justify-content: space-between; align-items: center; padding: 24px 24px 0 24px; margin-bottom: 16px; } .uets-response-popup-title { font-weight: 600; font-size: 22px; color: var(--md-on-surface); line-height: 28px; } .uets-response-popup-close { background: none; border: none; width: 48px; height: 48px; border-radius: 24px; cursor: pointer; color: var(--md-on-surface-variant); transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); display: flex; align-items: center; justify-content: center; font-family: 'Material Icons Outlined'; font-size: 24px; } .uets-response-popup-close::before { content: 'close'; } .uets-response-popup-close:hover { background: rgba(103, 80, 164, 0.08); color: var(--md-primary); } .uets-response-popup-content { white-space: pre-wrap; font-size: 14px; line-height: 20px; color: var(--md-on-surface); padding: 0 24px 24px 24px; max-height: calc(80vh - 120px); overflow-y: auto; } .uets-response-popup-content strong, .uets-response-popup-content b { color: var(--md-primary); font-weight: 600; } .uets-response-popup-loading { text-align: center; font-style: normal; color: var(--md-on-surface-variant); padding: 40px 24px; font-size: 16px; display: flex; flex-direction: column; align-items: center; gap: 16px; } .uets-loading-spinner { width: 32px; height: 32px; border: 3px solid var(--md-outline-variant); border-top: 3px solid var(--md-primary); border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #uets-toggle-ui-button { position: fixed; bottom: 20px; left: 20px; z-index: 10002; background: var(--md-primary-container); color: var(--md-on-primary-container); border: none; border-radius: 16px; width: 56px; height: 56px; cursor: pointer; box-shadow: 0 3px 5px rgba(0,0,0,0.2), 0 6px 10px rgba(0,0,0,0.14), 0 1px 18px rgba(0,0,0,0.12); transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); user-select: none; display: flex; align-items: center; justify-content: center; font-family: 'Material Icons Outlined'; font-size: 24px; } #uets-toggle-ui-button:hover { box-shadow: 0 5px 5px rgba(0,0,0,0.2), 0 9px 18px rgba(0,0,0,0.14), 0 3px 14px rgba(0,0,0,0.12); transform: scale(1.05); } #uets-toggle-ui-button.uets-mods-hidden-state { background: transparent; box-shadow: none; } #uets-toggle-ui-button.uets-mods-hidden-state:hover { background: rgba(103, 80, 164, 0.08); box-shadow: none; } .uets-correct-answer { background: rgba(76, 175, 80, 0.12) !important; border: 2px solid #4CAF50 !important; border-radius: 12px !important; } .uets-answer-indicator { position: absolute; top: 8px; right: 8px; background: #4CAF50; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; z-index: 1000; font-family: 'Material Icons Outlined'; display: flex; align-items: center; justify-content: center; } .uets-answer-indicator::before { content: 'check'; font-size: 16px; } .uets-streak-bonus { margin-left: 8px; color: #FFD700; font-weight: 600; font-size: 14px; text-shadow: 1px 1px 2px rgba(0,0,0,0.3); font-family: 'Roboto', sans-serif; } .uets-config-gui { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--md-surface); color: var(--md-on-surface); border-radius: 28px; padding: 0; z-index: 10003; width: 640px; max-width: 90vw; max-height: 90vh; overflow: hidden; box-shadow: 0 24px 38px rgba(0,0,0,0.14), 0 9px 46px rgba(0,0,0,0.12), 0 11px 15px rgba(0,0,0,0.20); font-family: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif; } .uets-config-header { display: flex; justify-content: space-between; align-items: center; padding: 24px 24px 12px 24px; border-bottom: 1px solid var(--md-outline-variant); } .uets-config-title { font-size: 24px; font-weight: 400; color: var(--md-on-surface); line-height: 32px; letter-spacing: 0px; } .uets-config-close { background: none; border: none; width: 40px; height: 40px; border-radius: 20px; cursor: pointer; color: var(--md-on-surface-variant); transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); display: flex; align-items: center; justify-content: center; font-family: 'Material Icons Outlined'; font-size: 20px; } .uets-config-close::before { content: 'close'; } .uets-config-close:hover { background: var(--md-surface-container-highest); color: var(--md-on-surface); } .uets-config-content { max-height: calc(90vh - 200px); overflow-y: auto; } .uets-config-section { margin-bottom: 8px; padding: 16px 24px; } .uets-config-section-title { font-size: 16px; font-weight: 500; margin-bottom: 16px; color: var(--md-primary); line-height: 24px; letter-spacing: 0.1px; } .uets-config-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 0; min-height: 56px; } .uets-config-label-container { display: flex; align-items: center; flex: 1; margin-right: 16px; } .uets-config-label { font-size: 16px; font-weight: 400; color: var(--md-on-surface); margin-left: 12px; line-height: 24px; letter-spacing: 0.5px; } .uets-config-input, .uets-config-select { background: var(--md-surface-container-highest); border: 1px solid var(--md-outline); border-radius: 4px; padding: 16px; color: var(--md-on-surface); font-size: 16px; font-family: 'Roboto', sans-serif; width: 200px; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); box-sizing: border-box; } .uets-config-input:focus, .uets-config-select:focus { outline: none; border-color: var(--md-primary); border-width: 2px; padding: 15px; } .uets-config-input:disabled { background: var(--md-surface-variant); color: var(--md-on-surface-variant); border-color: var(--md-outline-variant); } .uets-switch { position: relative; display: inline-block; width: 52px; height: 32px; cursor: pointer; } .uets-switch input { opacity: 0; width: 0; height: 0; } .uets-switch-slider { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: var(--md-outline); border-radius: 16px; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); border: 2px solid var(--md-outline); } .uets-switch-slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 4px; bottom: 4px; background: var(--md-surface-container-highest); border-radius: 50%; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); box-shadow: 0 1px 3px rgba(0,0,0,0.4); } .uets-switch input:checked + .uets-switch-slider { background: var(--md-primary); border-color: var(--md-primary); } .uets-switch input:checked + .uets-switch-slider:before { transform: translateX(20px); background: var(--md-on-primary); } .uets-switch:hover .uets-switch-slider { box-shadow: 0 0 0 8px rgba(103, 80, 164, 0.04); } .uets-switch input:checked:hover + .uets-switch-slider { box-shadow: 0 0 0 8px rgba(103, 80, 164, 0.08); } .uets-config-info { background: var(--md-secondary-container); color: var(--md-on-secondary-container); border: none; border-radius: 50%; width: 22px; height: 22px; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-family: 'Material Icons Outlined'; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); font-weight: 500; } .uets-config-info::before { content: 'help'; font-size: 20px; } .uets-config-info:hover { background: var(--md-secondary); color: var(--md-on-secondary); transform: scale(1.1); } .uets-config-buttons { display: flex; justify-content: flex-end; gap: 8px; padding: 16px 24px 24px 24px; border-top: 1px solid var(--md-outline-variant); background: var(--md-surface-container-low); } .uets-config-button { padding: 10px 24px; border: none; border-radius: 20px; cursor: pointer; font-size: 14px; font-weight: 500; font-family: 'Roboto', sans-serif; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); display: inline-flex; align-items: center; gap: 8px; min-height: 40px; justify-content: center; letter-spacing: 0.1px; } .uets-config-save { background: var(--md-primary); color: var(--md-on-primary); } .uets-config-save::before { content: 'save'; font-family: 'Material Icons Outlined'; font-size: 18px; } .uets-config-save:hover { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); background: #5a4089; } .uets-config-reset { background: var(--md-error); color: var(--md-on-error); } .uets-config-reset::before { content: 'refresh'; font-family: 'Material Icons Outlined'; font-size: 18px; } .uets-config-reset:hover { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); background: #a02117; } .uets-config-cancel { background: transparent; color: var(--md-primary); border: 1px solid var(--md-outline); } .uets-config-cancel::before { content: 'cancel'; font-family: 'Material Icons Outlined'; font-size: 18px; } .uets-config-cancel:hover { background: var(--md-surface-container-highest); border-color: var(--md-primary); } .uets-config-content::-webkit-scrollbar { width: 8px; } .uets-config-content::-webkit-scrollbar-track { background: var(--md-surface-container-low); } .uets-config-content::-webkit-scrollbar-thumb { background: var(--md-outline-variant); border-radius: 4px; } .uets-config-content::-webkit-scrollbar-thumb:hover { background: var(--md-outline); } .uets-profile-selector { margin: 4px 4px 4px 4px; padding: 16px; background: var(--md-surface-container-low); border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.12); } .uets-profile-list { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--md-outline-variant) transparent; } .uets-profile-list::-webkit-scrollbar { height: 6px; } .uets-profile-list::-webkit-scrollbar-track { background: transparent; } .uets-profile-list::-webkit-scrollbar-thumb { background: var(--md-outline-variant); border-radius: 3px; } .uets-profile-list::-webkit-scrollbar-thumb:hover { background: var(--md-outline); } .uets-profile-button { background: var(--md-surface-container-highest); color: var(--md-on-surface); border: 1px solid var(--md-outline); border-radius: 20px; padding: 8px 16px; font-family: 'Roboto', sans-serif; font-weight: 500; font-size: 14px; cursor: pointer; white-space: nowrap; transition: all 0.2s cubic-bezier(0.2, 0, 0, 1); flex-shrink: 0; } .uets-profile-button:hover { background: var(--md-surface-container); border-color: var(--md-primary); } .uets-profile-button.active { background: var(--md-primary); color: var(--md-on-primary); border-color: var(--md-primary); } .uets-profile-button.active:hover { background: #5a4089; } /* Dark mode overrides */ @media (prefers-color-scheme: dark) { :root { --md-primary: #D0BCFF; --md-primary-container: #4F378B; --md-on-primary: #371E73; --md-on-primary-container: #EADDFF; --md-secondary: #CCC2DC; --md-secondary-container: #4A4458; --md-on-secondary: #332D41; --md-on-secondary-container: #E8DEF8; --md-tertiary: #EFB8C8; --md-tertiary-container: #633B48; --md-on-tertiary: #492532; --md-on-tertiary-container: #FFD8E4; --md-surface: #141218; --md-surface-dim: #141218; --md-surface-bright: #3B383E; --md-surface-container-lowest: #0F0D13; --md-surface-container-low: #1D1B20; --md-surface-container: #211F26; --md-surface-container-high: #2B2930; --md-surface-container-highest: #36343B; --md-on-surface: #E6E0E9; --md-on-surface-variant: #CAC4D0; --md-outline: #938F99; --md-outline-variant: #49454F; --md-error: #F2B8B5; --md-error-container: #8C1D18; --md-on-error: #601410; --md-on-error-container: #F9DEDC; --md-shadow: #000000; } } /* Add Kahoot-specific styles */ .kahoot-answer-indicator { position: absolute; top: 12px; right: 18px; min-width: 24px; height: 24px; background: linear-gradient(135deg, rgba(0,0,0,0.85) 60%, rgba(0,0,0,0.7) 100%); color: #fff; padding: 0 8px; border-radius: 12px; font-size: 15px; font-weight: bold; box-shadow: 0 2px 8px rgba(0,0,0,0.18); display: flex; align-items: center; justify-content: center; border: 2px solid #fff2; z-index: 1000; pointer-events: none; user-select: none; transition: transform 0.15s; } .kahoot-answer-button { position: relative; } `) // === WELCOME POPUP FOR NEW USERS === const showWelcomePopup = () => { if (!sharedState.uiModificationsEnabled) return; const popup = document.createElement("div"); popup.classList.add("uets-welcome-popup"); popup.id = "uets-welcome-popup"; const header = document.createElement("div"); header.classList.add("uets-response-popup-header"); const title = document.createElement("span"); title.classList.add("uets-response-popup-title"); title.textContent = "Welcome to UETS!"; const closeButton = document.createElement("button"); closeButton.classList.add("uets-response-popup-close"); closeButton.onclick = () => popup.remove(); header.appendChild(title); header.appendChild(closeButton); popup.appendChild(header); const content = document.createElement("div"); content.classList.add("uets-response-popup-content"); content.innerHTML = `<p>- Press the floating button (bottom-left) to activate/deactivate visual changes on the page.\n- Press and release the button 3 times within 2 seconds to open the settings menu.\n- In the settings, click on the info button on the left side of each option to get some insight into the setting.</p>`; popup.appendChild(content); document.body.appendChild(popup); }; // === SHARED UTILITIES === const createButton = (text, className, onClick) => { const button = document.createElement("button"); button.textContent = text; button.classList.add(...className.split(" ")); button.onclick = onClick; return button; }; const createLink = (text, href, className, onClick) => { const link = document.createElement("a"); link.textContent = text; link.href = href; link.target = "_blank"; link.rel = "noopener noreferrer"; link.classList.add(...className.split(" ")); if (onClick) link.onclick = onClick; return link; }; const addQuestionButtons = ( container, questionText, options, imageUrl, platform, includeGetAnswer = false, ddgText = "DDG", ) => { const buttonsContainer = document.createElement("div"); buttonsContainer.classList.add("uets-main-question-buttons-container"); sharedState.elementsToCleanup.push(buttonsContainer); const ddgLink = createLink( ddgText, `https://duckduckgo.com/?q=${encodeURIComponent(questionText || "question")}`, "uets-ddg-link uets-ddg-link-main-question", ); buttonsContainer.appendChild(ddgLink); const copyPromptButton = createButton( "Prompt", "uets-copy-prompt-button uets-copy-prompt-button-main-question", async () => { const prompt = buildGeminiPrompt( questionText || "(See attached image)", options, !!imageUrl, platform, ); try { await navigator.clipboard.writeText(prompt); copyPromptButton.textContent = "Copied!"; setTimeout( () => (copyPromptButton.textContent = "Prompt"), 2000, ); } catch (err) { alert("Failed to copy prompt."); } }, ); buttonsContainer.appendChild(copyPromptButton); const geminiButton = createButton( "Ask AI", "uets-gemini-button uets-gemini-button-main-question", async () => { let imageData = null; if (imageUrl && sharedState.config.includeImages) { try { imageData = await fetchImageAsBase64(imageUrl); } catch (error) { showResponsePopup( `Failed to fetch image: ${error}\nProceeding with text only.`, ); } } askGemini( questionText || "(See attached image)", options, imageData, platform, ); }, ); buttonsContainer.appendChild(geminiButton); if (includeGetAnswer) { const getAnswerButton = createButton( "Get Answer", "uets-get-answer-button", async () => { const questionData = sharedState.quizData[sharedState.currentQuestionId]; if (questionData) { const response = await sendQuestionToServer( sharedState.currentQuestionId, questionData.type, questionData.structure.options ? questionData.structure.options.map((opt) => opt.id) : [], ); if (response && response.hasAnswer) { GM_log("[*] Received answer from server:", response); highlightCorrectAnswers( response.correctAnswers, response.questionType, ); } else { showResponsePopup("No answer available yet."); } } else { showResponsePopup("Question data not found."); } }, ); buttonsContainer.appendChild(getAnswerButton); } container.appendChild(buttonsContainer); }; const processProceedGameRequest = (data) => { if (data.response && data.response.timeTaken !== undefined && sharedState.config.enableTimeTakenEdit) { const timetakenforce = Math.floor(Math.random() * (sharedState.config.timeTakenMax - sharedState.config.timeTakenMin + 1)) + sharedState.config.timeTakenMin; data.response.timeTaken = timetakenforce; if (sharedState.config.enableTimerHijack) { data.response.provisional.scoreBreakups.correct.timer = sharedState.config.timerBonusPoints; data.response.provisional.scoreBreakups.correct.total = sharedState.config.timerBonusPoints + data.response.provisional.scoreBreakups.correct.base + data.response.provisional.scoreBreakups.correct.streak; data.response.provisional.scores.correct = sharedState.config.timerBonusPoints + data.response.provisional.scoreBreakups.correct.base + data.response.provisional.scoreBreakups.correct.streak; } GM_log( `[+] timeTaken modified from ${data.response.timeTaken} to ${timetakenforce}`, ); } return data; }; const processProceedGameResponse = (data) => { try { var questionId = data.response.questionId; var correctAnswer = data.question.structure.answer; if (correctAnswer == 0 && data.question.structure.options !== undefined) { correctAnswer = data.question.structure.options[0].text; } } catch (e) { var questionId = data.data.response.questionId; var correctAnswer = data.data.question.structure.answer; if (correctAnswer == 0 && data.data.question.structure.options !== undefined) { correctAnswer = data.data.question.structure.options[0].text; } } GM_log(`[*] Sending correct answer (${questionId} <${correctAnswer}>) to server`); sendAnswerToServer(questionId, correctAnswer); }; // === SPOOF FULLSCREEN AND FOCUS === const spoofFullscreenAndFocus = () => { // Spoof fullscreen properties Object.defineProperty(document, "fullscreenElement", { get: () => document.documentElement, configurable: true, }); Object.defineProperty(document, "fullscreen", { get: () => true, configurable: true, }); // Spoof focus properties - hasFocus is a method Object.defineProperty(document, "hasFocus", { value: () => true, writable: true, configurable: true, }); // Override window.focus to do nothing const originalFocus = window.focus; window.focus = () => { // Simulate focus without actually focusing }; // Spoof visibility state Object.defineProperty(document, "visibilityState", { get: () => "visible", configurable: true, }); // Prevent visibilitychange events from firing or handle them const originalDispatchEvent = document.dispatchEvent; document.dispatchEvent = function (event) { if (event.type === "visibilitychange") { // Suppress or modify visibilitychange events return true; } return originalDispatchEvent.call(this, event); }; // Remove toast manager to prevent spam const removeToastManager = () => { const toastManager = document.querySelector(".toast-manager"); if (toastManager) toastManager.remove(); }; // Observe for toast manager const toastObserver = new MutationObserver(() => { removeToastManager(); }); toastObserver.observe(document.body, { childList: true, subtree: true }); // Initial check removeToastManager(); GM_log("[+] Fullscreen and focus spoofing enabled."); }; // === SERVER COMMUNICATION === const sendQuestionToServer = async (questionId, questionType, answerIds) => { try { const response = await fetch(`${sharedState.config.serverUrl}/api/question`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ questionId, questionType, answerIds }), }); return await response.json(); } catch (error) { GM_log("[!] Error sending question to server:", error); return null; } }; const sendAnswerToServer = async (questionId, correctAnswers) => { try { const response = await fetch(`${sharedState.config.serverUrl}/api/answer`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ questionId, correctAnswers }), }); return await response.json(); } catch (error) { GM_log("[!] Error sending answer to server:", error); return null; } }; // === ANSWER HIGHLIGHTING === const highlightCorrectAnswers = (correctAnswers, questionType) => { if (!sharedState.uiModificationsEnabled) return; const optionButtons = document.querySelectorAll("button.option"); optionButtons.forEach((button) => { button.classList.remove("uets-correct-answer"); const indicator = button.querySelector(".uets-answer-indicator"); if (indicator) indicator.remove(); }); if (questionType === "BLANK") { showCorrectAnswersModal(correctAnswers, questionType); return; } // Get the current question data to match answer IDs to text const currentQuestion = sharedState.questionsPool[sharedState.currentQuestionId]; if (!currentQuestion) { showCorrectAnswersModal(correctAnswers, questionType); return; } // Display the correct answers in a modal showCorrectAnswersModal(correctAnswers, questionType, currentQuestion); // Still highlight the buttons if possible optionButtons.forEach((button) => { const optionId = button.getAttribute("data-option-id") || button .querySelector("[data-option-id]") ?.getAttribute("data-option-id"); if (optionId && correctAnswers.includes(optionId)) { button.classList.add("uets-correct-answer"); button.style.position = "relative"; const indicator = document.createElement("div"); indicator.className = "uets-answer-indicator"; indicator.textContent = "✓"; button.appendChild(indicator); } }); }; // === NEW: SHOW CORRECT ANSWERS MODAL === const showCorrectAnswersModal = ( correctAnswers, questionType, questionData = null, ) => { if (!sharedState.uiModificationsEnabled) return; let content = ""; if (questionType === "BLANK") { content = `${correctAnswers}`; } else if (questionType === "MCQ") { const correctIndex = correctAnswers; if (!questionData && sharedState.currentQuestionId) { questionData = sharedState.questionsPool[sharedState.currentQuestionId]; } if ( questionData && questionData.structure && questionData.structure.options && questionData.structure.options[correctIndex] ) { const div = document.createElement("div"); div.innerHTML = questionData.structure.options[correctIndex].text; content = `${div.textContent.trim()}`; } else { content = `Option ${correctIndex + 1}`; } } else if (questionType === "MSQ") { if (!questionData && sharedState.currentQuestionId) { questionData = sharedState.questionsPool[sharedState.currentQuestionId]; } if ( questionData && questionData.structure && questionData.structure.options ) { const correctOptions = correctAnswers.map((index) => { if (questionData.structure.options[index]) { const div = document.createElement("div"); div.innerHTML = questionData.structure.options[index].text; return div.textContent.trim(); } return `Option ${index + 1}`; }); content = `${correctOptions.join("\n")}`; } else { content = `Options ${correctAnswers.map((i) => i + 1).join(", ")}`; } } else { if (Array.isArray(correctAnswers)) { content = `${correctAnswers.join(", ")}`; } else { content = `${correctAnswers}`; } } showResponsePopup(content, false, "Correct Answers"); }; // === CONFIG MANAGEMENT === const saveConfig = () => { GM_setValue(CONFIG_STORAGE_KEY, sharedState.config); GM_setValue(GEMINI_API_KEY_STORAGE, sharedState.config.geminiApiKey); }; const resetConfig = () => { sharedState.config = { ...DEFAULT_CONFIG }; saveConfig(); }; const createConfigGui = () => { if (sharedState.uiModificationsEnabled === false) { handleToggleUiClick(); } if (sharedState.configGui) return; const gui = document.createElement('div'); gui.className = 'uets-config-gui'; gui.innerHTML = ` <div class="uets-config-header"> <span class="uets-config-title">UETS Configuration</span> <button class="uets-config-close"></button> </div> <div class="uets-config-content"> <div class="uets-profile-selector"> <div class="uets-profile-list"> <button class="uets-profile-button" data-profile="Custom">Custom</button> <button class="uets-profile-button" data-profile="Stealthy">Stealthy</button> <button class="uets-profile-button" data-profile="Creator's choice">Creator's choice</button> <button class="uets-profile-button" data-profile="LMAO">LMAO</button> </div> </div> <div class="uets-card" style="margin-left: 4px; margin-right: 4px;"> <div class="uets-config-section"> <div class="uets-config-section-title">General Settings</div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Makes the website think that it's in fullscreen mode AND focused. Recommended on Testportal and Wayground (if the teacher enabled extra protections)." title="Info"></button> <label class="uets-config-label">Fullscreen spoofing</label> </div> <label class="uets-switch"> <input type="checkbox" id="enableSpoofFullscreen"> <span class="uets-switch-slider"></span> </label> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="URL of the server for storing and retrieving answers." title="Info"></button> <label class="uets-config-label">Server URL</label> </div> <input type="text" class="uets-config-input" id="serverUrl" style="width: 200px;"> </div> </div> </div> <div class="uets-card" style="margin-top: 6px; margin-left: 4px; margin-right: 4px;"> <div class="uets-config-section"> <div class="uets-config-section-title">Wayground Settings</div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Allows you to spoof the time taken to answer a question. You have 20 seconds to get a ~100-400 point bonus per question, this option forces the site to give you bonus points." title="Info"></button> <label class="uets-config-label">Hijack timeTaken</label> </div> <label class="uets-switch"> <input type="checkbox" id="enableTimeTakenEdit"> <span class="uets-switch-slider"></span> </label> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Minimum time in milliseconds for randomized time taken. I recommend keeping it between 6000 and 9000. Very low values will alert the teacher." title="Info"></button> <label class="uets-config-label">timeTaken min (ms)</label> </div> <input type="number" class="uets-config-input" id="timeTakenMin" min="100" max="60000"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Maximum time in milliseconds for randomized time taken. I recommend keeping it between 7000 and 12000. Very low values will alert the teacher." title="Info"></button> <label class="uets-config-label">timeTaken max (ms)</label> </div> <input type="number" class="uets-config-input" id="timeTakenMax" min="100" max="60000"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Enables timer hijacking, this adds bonus points to your score, I recommend keeping it between 200 and 350 points to seem legitimate." title="Info"></button> <label class="uets-config-label">Hijack timer for points</label> </div> <label class="uets-switch"> <input type="checkbox" id="enableTimerHijack"> <span class="uets-switch-slider"></span> </label> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Timer bonus that you'll recieve if you enable timer hijacking. Values above 6000 are problematic and will cause issues. Maximum value that's guaranteed to work is 5000, but I don't recommend setting it to anything above 350." title="Info"></button> <label class="uets-config-label">Timer bonus points</label> </div> <input type="number" class="uets-config-input" id="timerBonusPoints" min="0" max="5000"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="DANGER: Enabling this and clicking any reaction will trigger a wave of reactions being shown on the teacher's screen, alongside potentially freezing your browser. If you want to use it, try your best not to get caught." title="Info"></button> <label class="uets-config-label">Enable reaction spam</label> </div> <label class="uets-switch"> <input type="checkbox" id="enableReactionSpam"> <span class="uets-switch-slider"></span> </label> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="How many reactions should be set per one reaction." title="Info"></button> <label class="uets-config-label">Reaction spam count</label> </div> <input type="number" class="uets-config-input" id="reactionSpamCount" min="0" max="10"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Delay in milliseconds between each resend." title="Info"></button> <label class="uets-config-label">Reaction spam delay (ms)</label> </div> <input type="number" class="uets-config-input" id="reactionSpamDelay" min="50" max="1000"> </div> </div> </div> <div class="uets-card" style="margin-top: 6px; margin-left: 4px; margin-right: 4px;"> <div class="uets-config-section"> <div class="uets-config-section-title">Gemini AI Settings</div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Your Gemini API key for AI assistance. You can get it on https://aistudio.google.com/apikey." title="Info"></button> <label class="uets-config-label">API Key</label> </div> <input type="password" class="uets-config-input" id="geminiApiKey" style="width: 200px;"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="The Gemini model to use for AI queries. I recommend using the flash or lite series of models for quick answers. Breakdown (Requests/min / Tokens/min):\n\n2.5 Pro - Slower responses, costs more tokens, best quality. 5/250k.\n2.5 Flash - Fast responses, lower ratelimit, good quality. 10/250k.\n2.5 Flash-Lite - Fastest responses, lowest ratelimit, okay quality. 15/250k.\n2.0 Flash - Fast responses, lower ratelimit, acceptable quality. 15/1M.\n2.0 Flash-Lite - Fastest responses, lowest ratelimit, acceptable quality. 30/1M." title="Info"></button> <label class="uets-config-label">Model</label> </div> <select class="uets-config-input" id="geminiModel"><option>Loading models...</option></select> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Whether to include question images in AI prompts." title="Info"></button> <label class="uets-config-label">Include images</label> </div> <label class="uets-switch"> <input type="checkbox" id="includeImages"> <span class="uets-switch-slider"></span> </label> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Budget for thinking in the AI model (0 disables thinking). Can increase output response quality, but increases the waiting time for the answer. I recommend 256 or 512 tokens." title="Info"></button> <label class="uets-config-label">Thinking budget</label> </div> <input type="number" class="uets-config-input" id="thinkingBudget" min="0" max="4096"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Maximum number of tokens in AI responses (1-8192). A token is roughly equal to a word, or a punctuation mark." title="Info"></button> <label class="uets-config-label">Max output tokens</label> </div> <input type="number" class="uets-config-input" id="maxOutputTokens" min="1" max="8192"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Controls randomness (or creativity) in AI responses (0-2). Lower values will be coherant and predictable, while larger values will be more creating. A value between 0.2 and 0.5 is recommended." title="Info"></button> <label class="uets-config-label">Temperature</label> </div> <input type="number" class="uets-config-input" id="temperature" min="0" max="2" step="0.1"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Computes the cumulative probability distribution, and cut off as soon as that distribution exceeds the value of topP. Basically how many words can be computed and considered. I recommend a value between 0.90 and 1.00 for a better quality output." title="Info"></button> <label class="uets-config-label">Top P</label> </div> <input type="number" class="uets-config-input" id="topP" min="0" max="1" step="0.05"> </div> <div class="uets-config-item"> <div class="uets-config-label-container"> <button class="uets-config-info" data-info="Helps balance creativity and coherence in generated text by introducing controlled randomness while avoiding less likely or nonsensical words. Basically avoids obscure words, I recommend a value between 40 and 64." title="Info"></button> <label class="uets-config-label">Top K</label> </div> <input type="number" class="uets-config-input" id="topK" min="1" max="100"> </div> </div> </div> </div> <div class="uets-config-buttons"> <button class="uets-config-button uets-config-save">Save</button> <button class="uets-config-button uets-config-reset">Reset to Defaults</button> <button class="uets-config-button uets-config-cancel">Cancel</button> </div> `; // Populate current values const populateValues = () => { document.getElementById('enableTimeTakenEdit').checked = sharedState.config.enableTimeTakenEdit; document.getElementById('timeTakenMin').value = sharedState.config.timeTakenMin; document.getElementById('timeTakenMax').value = sharedState.config.timeTakenMax; document.getElementById('enableTimerHijack').checked = sharedState.config.enableTimerHijack; document.getElementById('timerBonusPoints').value = sharedState.config.timerBonusPoints; document.getElementById('enableSpoofFullscreen').checked = sharedState.config.enableSpoofFullscreen; document.getElementById('serverUrl').value = sharedState.config.serverUrl; document.getElementById('geminiApiKey').value = sharedState.config.geminiApiKey; document.getElementById('includeImages').checked = sharedState.config.includeImages; document.getElementById('thinkingBudget').value = sharedState.config.thinkingBudget; document.getElementById('maxOutputTokens').value = sharedState.config.maxOutputTokens; document.getElementById('temperature').value = sharedState.config.temperature; document.getElementById('topP').value = sharedState.config.topP; document.getElementById('topK').value = sharedState.config.topK; document.getElementById('enableReactionSpam').checked = sharedState.config.enableReactionSpam; document.getElementById('reactionSpamCount').value = sharedState.config.reactionSpamCount; document.getElementById('reactionSpamDelay').value = sharedState.config.reactionSpamDelay; // Set active profile button const profileButtons = gui.querySelectorAll('.uets-profile-button'); profileButtons.forEach(btn => btn.classList.remove('active')); const customBtn = gui.querySelector('.uets-profile-button[data-profile="Custom"]'); if (customBtn) customBtn.classList.add('active'); }; // Event handlers gui.querySelector('.uets-config-close').onclick = () => closeConfigGui(); gui.querySelector('.uets-config-cancel').onclick = () => closeConfigGui(); gui.querySelector('.uets-config-save').onclick = () => { // Collect values sharedState.config.enableTimeTakenEdit = document.getElementById('enableTimeTakenEdit').checked; sharedState.config.timeTakenMin = parseInt(document.getElementById('timeTakenMin').value); sharedState.config.timeTakenMax = parseInt(document.getElementById('timeTakenMax').value); sharedState.config.enableTimerHijack = document.getElementById('enableTimerHijack').checked; sharedState.config.timerBonusPoints = parseInt(document.getElementById('timerBonusPoints').value); sharedState.config.enableSpoofFullscreen = document.getElementById('enableSpoofFullscreen').checked; sharedState.config.serverUrl = document.getElementById('serverUrl').value; sharedState.config.geminiApiKey = document.getElementById('geminiApiKey').value; sharedState.config.geminiModel = document.getElementById('geminiModel').value; sharedState.config.includeImages = document.getElementById('includeImages').checked; sharedState.config.thinkingBudget = parseInt(document.getElementById('thinkingBudget').value); sharedState.config.maxOutputTokens = parseInt(document.getElementById('maxOutputTokens').value); sharedState.config.temperature = parseFloat(document.getElementById('temperature').value); sharedState.config.topP = parseFloat(document.getElementById('topP').value); sharedState.config.topK = parseInt(document.getElementById('topK').value); sharedState.config.enableReactionSpam = document.getElementById('enableReactionSpam').checked; sharedState.config.reactionSpamCount = parseInt(document.getElementById('reactionSpamCount').value); sharedState.config.reactionSpamDelay = parseInt(document.getElementById('reactionSpamDelay').value); saveConfig(); closeConfigGui(); alert('Configuration saved!'); }; gui.querySelector('.uets-config-reset').onclick = () => { if (confirm('Reset all settings to defaults?')) { resetConfig(); populateValues(); // Re-fetch models after reset fetchGeminiModels().then(models => { const select = document.getElementById('geminiModel'); if (select) { select.innerHTML = models.map(m => `<option value="${m.name.replace('models/', '')}">${m.displayName}</option>`).join(''); select.value = sharedState.config.geminiModel; } }); } }; // Add event listeners for profile buttons const profileButtons = gui.querySelectorAll('.uets-profile-button'); profileButtons.forEach(btn => { btn.addEventListener('click', () => { // Remove active class from all profileButtons.forEach(b => b.classList.remove('active')); // Add to clicked btn.classList.add('active'); const selectedProfile = btn.getAttribute('data-profile'); if (selectedProfile !== 'Custom' && PROFILES[selectedProfile]) { const profile = PROFILES[selectedProfile]; document.getElementById('enableTimeTakenEdit').checked = profile.enableTimeTakenEdit; document.getElementById('timeTakenMin').value = profile.timeTakenMin; document.getElementById('timeTakenMax').value = profile.timeTakenMax; document.getElementById('enableTimerHijack').checked = profile.enableTimerHijack; document.getElementById('timerBonusPoints').value = profile.timerBonusPoints; document.getElementById('enableSpoofFullscreen').checked = profile.enableSpoofFullscreen; } }); }); document.body.appendChild(gui); sharedState.configGui = gui; populateValues(); // Fetch and populate models fetchGeminiModels().then(models => { const select = document.getElementById('geminiModel'); if (select) { select.innerHTML = models.map(m => `<option value="${m.name.replace('models/', '')}">${m.displayName}</option>`).join(''); select.value = sharedState.config.geminiModel; } }).catch(() => { const select = document.getElementById('geminiModel'); if (select) { select.innerHTML = '<option value="">Failed to load models</option>'; } }); // Add event listeners for info buttons const infoButtons = gui.querySelectorAll('.uets-config-info'); infoButtons.forEach(btn => { btn.onclick = () => { const info = btn.getAttribute('data-info'); showResponsePopup(info, false, "Option Info"); }; }); }; const closeConfigGui = () => { if (sharedState.configGui) { sharedState.configGui.remove(); sharedState.configGui = null; } }; // === SHARED API KEY MANAGEMENT === GM_registerMenuCommand("Set Gemini API Key", () => { const currentKey = GM_getValue(GEMINI_API_KEY_STORAGE, ""); const newKey = prompt("Enter your Gemini API Key:", currentKey); if (newKey !== null) { GM_setValue(GEMINI_API_KEY_STORAGE, newKey.trim()); sharedState.config.geminiApiKey = newKey.trim(); GM_log("[+] Gemini API Key updated."); alert("Gemini API Key saved!"); } }); const getApiKey = async () => { let apiKey = sharedState.config.geminiApiKey || GM_getValue(GEMINI_API_KEY_STORAGE, null); if (!apiKey || apiKey.trim() === "") { apiKey = prompt("Gemini API Key not set. Please enter your API Key:"); if (apiKey && apiKey.trim() !== "") { sharedState.config.geminiApiKey = apiKey.trim(); GM_setValue(GEMINI_API_KEY_STORAGE, apiKey.trim()); saveConfig(); return apiKey.trim(); } else { alert("Gemini API Key is required. Set it via the configuration or Tampermonkey menu."); return null; } } return apiKey.trim(); }; // === SHARED MODEL FETCHING === const fetchGeminiModels = async () => { const apiKey = sharedState.config.geminiApiKey; if (!apiKey || apiKey.trim() === "") return []; let models = []; let pageToken = ''; do { const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}${pageToken ? '&pageToken=' + pageToken : ''}`; try { const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, onload: (res) => { if (res.status === 200) { resolve(JSON.parse(res.responseText)); } else { reject(new Error(`HTTP ${res.status}: ${res.responseText}`)); } }, onerror: (err) => reject(err), }); }); const filteredModels = response.models.filter( (m) => m.supportedGenerationMethods.includes("generateContent") && m.supportedGenerationMethods.includes("countTokens") && m.name.includes('gemini') && m.name.includes('2.') && !m.name.toLowerCase().includes('tts') && !m.name.toLowerCase().includes('live') ); models.push(...filteredModels); pageToken = response.nextPageToken; } catch (error) { GM_log("[!] Error fetching Gemini models:", error); break; } } while (pageToken); // Sort: models without 'preview' or 'experimental' first, then with 'preview' or 'experimental' models.sort((a, b) => { const aHasPreviewOrExperimental = a.name.toLowerCase().includes('preview') || a.name.toLowerCase().includes('experimental'); const bHasPreviewOrExperimental = b.name.toLowerCase().includes('preview') || b.name.toLowerCase().includes('experimental'); if (aHasPreviewOrExperimental && !bHasPreviewOrExperimental) return 1; if (!aHasPreviewOrExperimental && bHasPreviewOrExperimental) return -1; return 0; }); return models; }; // === SHARED IMAGE FETCHING === const fetchImageAsBase64 = (imageUrl) => new Promise((resolve, reject) => { GM_log(`[*] Fetching image: ${imageUrl}...`); GM_xmlhttpRequest({ method: "GET", url: imageUrl, responseType: "blob", onload: (response) => { if (response.status >= 200 && response.status < 300) { const blob = response.response; const reader = new FileReader(); reader.onloadend = () => { const dataUrl = reader.result; const mimeType = dataUrl.substring( dataUrl.indexOf(":") + 1, dataUrl.indexOf(";"), ); const base64Data = dataUrl.substring(dataUrl.indexOf(",") + 1); GM_log(`[+] Image fetched successfully. MIME type: ${mimeType}`); resolve({ base64Data, mimeType }); }; reader.onerror = () => reject("FileReader error while processing image."); reader.readAsDataURL(blob); } else { reject(`Failed to fetch image. Status: ${response.status}`); } }, onerror: () => reject("Network error while fetching image."), ontimeout: () => reject("Image fetch request timed out."), }); }); // === SHARED GEMINI INTERACTION === const buildGeminiPrompt = ( question, options, hasImage, platform = "quiz", ) => { const contextMap = { quiz: "You are an AI assistant helping a user with a quiz.", test: "You are an AI assistant helping a user with a test.", form: "You are an AI assistant helping a user with a form.", }; return ` Context: ${contextMap[platform] || contextMap.quiz} The user needs to identify the correct answer(s) from the given options for the following question. ${hasImage ? "An image is associated with this question; please consider it in your analysis." : ""} Question: "${question}" Available Options: ${options.map((opt, i) => `${i + 1}. ${opt}`).join("\n")} Please perform the following: 1. Identify the correct answer or answers from the "Available Options" list. 2. Provide a concise reasoning for your choice(s). 3. Format your response clearly. Start with "Correct Answer(s):" followed by the answer(s), and then "Reasoning:" followed by your explanation. Be brief and to the point. `; }; const askGemini = async (question, options, imageData, platform = "quiz") => { const apiKey = await getApiKey(); if (!apiKey) return; const promptText = buildGeminiPrompt( question, options, !!imageData, platform, ); const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${sharedState.config.geminiModel}:generateContent?key=${apiKey}`; const requestPayloadContents = [{ parts: [{ text: promptText }] }]; if (sharedState.config.includeImages && imageData && imageData.base64Data && imageData.mimeType) { requestPayloadContents[0].parts.push({ inline_data: { mime_type: imageData.mimeType, data: imageData.base64Data, }, }); } const apiPayload = { contents: requestPayloadContents, generationConfig: { temperature: sharedState.config.temperature, topP: sharedState.config.topP, topK: sharedState.config.topK, maxOutputTokens: sharedState.config.maxOutputTokens, thinkingConfig: { thinkingBudget: sharedState.config.thinkingBudget, }, }, safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_MEDIUM_AND_ABOVE", }, { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE", }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_MEDIUM_AND_ABOVE", }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_MEDIUM_AND_ABOVE", }, ], }; showResponsePopup("Loading AI insights...", true, "AI Assistant"); GM_xmlhttpRequest({ method: "POST", url: apiUrl, headers: { "Content-Type": "application/json" }, data: JSON.stringify(apiPayload), onload: (response) => { try { const result = JSON.parse(response.responseText); if ( result.candidates && result.candidates.length > 0 && result.candidates[0].content && result.candidates[0].content.parts && result.candidates[0].content.parts.length > 0 ) { const geminiText = result.candidates[0].content.parts[0].text; GM_log("[+] Gemini API Response:", geminiText); showResponsePopup(geminiText, false, "AI Assistant"); } else if (result.error) { GM_log("[!] Gemini API Error:", result.error.message); showResponsePopup( `Gemini API Error: ${result.error.message}`, false, "AI Assistant", ); } else { showResponsePopup( "Gemini API Error: Could not parse a valid response.", false, "AI Assistant", ); } } catch (e) { showResponsePopup( "Gemini API Error: Failed to parse response.\n" + e.message, false, "AI Assistant", ); } }, onerror: (response) => { showResponsePopup( `Gemini API Error: Request failed. Status: ${response.status}`, false, "AI Assistant", ); }, ontimeout: () => showResponsePopup( "Gemini API Error: Request timed out.", false, "AI Assistant", ), }); }; const showResponsePopup = ( content, isLoading = false, title = "AI Assistant", ) => { if (!sharedState.uiModificationsEnabled) { if (sharedState.geminiPopup) { sharedState.geminiPopup.remove(); sharedState.geminiPopup = null; } return; } let popup = document.getElementById("uets-gemini-popup"); if (!popup) { popup = document.createElement("div"); popup.id = "uets-gemini-popup"; popup.classList.add("uets-response-popup"); const header = document.createElement("div"); header.classList.add("uets-response-popup-header"); const titleElement = document.createElement("span"); titleElement.classList.add("uets-response-popup-title"); titleElement.textContent = title; const closeButton = document.createElement("button"); closeButton.classList.add("uets-response-popup-close"); closeButton.onclick = () => { popup.remove(); sharedState.geminiPopup = null; }; header.appendChild(titleElement); header.appendChild(closeButton); popup.appendChild(header); const contentDiv = document.createElement("div"); contentDiv.classList.add("uets-response-popup-content"); popup.appendChild(contentDiv); document.body.appendChild(popup); sharedState.geminiPopup = popup; } else { const titleElement = popup.querySelector(".uets-response-popup-title"); if (titleElement) titleElement.textContent = title; } const contentDiv = popup.querySelector(".uets-response-popup-content"); if (isLoading) { contentDiv.innerHTML = ` <div class="uets-response-popup-loading"> <div class="uets-loading-spinner"></div> ${content} </div> `; } else { let formattedContent = content.replace( /^(Correct Answer\(s\):)/gim, "<strong>$1</strong>", ); formattedContent = formattedContent.replace( /^(Reasoning:)/gim, "<br><br><strong>$1</strong>", ); contentDiv.innerHTML = formattedContent; } popup.style.display = "block"; }; // === SHARED UI TOGGLE === const updateToggleButtonAppearance = () => { if (!sharedState.toggleButton) return; if (sharedState.uiModificationsEnabled) { sharedState.toggleButton.innerHTML = ""; sharedState.toggleButton.style.fontFamily = "'Material Icons Outlined'"; sharedState.toggleButton.style.fontSize = "24px"; sharedState.toggleButton.style.setProperty("--icon", "'close'"); sharedState.toggleButton.setAttribute("data-icon", "close"); sharedState.toggleButton.title = "Hide Tool Modifications"; sharedState.toggleButton.classList.remove("uets-mods-hidden-state"); } else { sharedState.toggleButton.innerHTML = ""; sharedState.toggleButton.style.fontFamily = "'Material Icons Outlined'"; sharedState.toggleButton.style.fontSize = "24px"; sharedState.toggleButton.style.setProperty("--icon", "'add'"); sharedState.toggleButton.setAttribute("data-icon", "add"); sharedState.toggleButton.title = "Show Tool Modifications"; sharedState.toggleButton.classList.add("uets-mods-hidden-state"); } // Set the icon content sharedState.toggleButton.style.setProperty("content", sharedState.uiModificationsEnabled ? "'close'" : "'add'"); const icon = sharedState.uiModificationsEnabled ? "close" : "add"; sharedState.toggleButton.textContent = icon; }; const handleToggleUiClick = () => { sharedState.uiModificationsEnabled = !sharedState.uiModificationsEnabled; GM_setValue(UI_MODS_ENABLED_KEY, sharedState.uiModificationsEnabled); updateToggleButtonAppearance(); if (!sharedState.uiModificationsEnabled) { document .querySelectorAll( ".uets-ddg-link, .uets-gemini-button, .uets-copy-prompt-button, .uets-get-answer-button, .uets-main-question-buttons-container, .uets-streak-bonus", ) .forEach((el) => el.remove()); if (sharedState.geminiPopup) { sharedState.geminiPopup.remove(); sharedState.geminiPopup = null; } document.querySelectorAll(".uets-option-wrapper").forEach((wrapper) => { const button = wrapper.querySelector("button.option"); if (button && wrapper.parentNode) { wrapper.parentNode.insertBefore(button, wrapper); } wrapper.remove(); }); sharedState.elementsToCleanup.forEach((el) => { if (el && el.parentNode && !el.querySelector("button.option")) { el.remove(); } }); sharedState.elementsToCleanup = []; if (sharedState.currentDomain.includes("wayground.com") || sharedState.currentDomain.includes("quizizz.com")) { // Revert text edits if (sharedState.originalTabLeaveHTML !== null) { const ruleDiv = document.querySelector('.test-mode-container'); if (ruleDiv) { ruleDiv.innerHTML = sharedState.originalTabLeaveHTML; } sharedState.originalTabLeaveHTML = null; } if (sharedState.originalStartButtonText !== null) { const startButton = document.querySelector('.start-game'); if (startButton) { const span = startButton.querySelector('span'); if (span) { span.textContent = sharedState.originalStartButtonText; } } sharedState.originalStartButtonText = null; } }; if (sharedState.currentDomain.includes("docs.google.com")) { const questionBlocks = document.querySelectorAll( 'div[role="listitem"] > div[jsmodel]', ); questionBlocks.forEach((block) => { delete block.dataset.uetsButtonsAdded; }); } if ( sharedState.currentDomain.includes("testportal.net") || sharedState.currentDomain.includes("testportal.pl") ) { const questionElements = document.querySelectorAll(".question_essence"); questionElements.forEach((el) => { delete el.dataset.enhancementsAdded; }); } } else { setTimeout(() => { initializeDomainSpecific(); }, 100); } }; const createToggleButton = () => { if (document.getElementById("uets-toggle-ui-button")) return; sharedState.toggleButton = document.createElement("button"); sharedState.toggleButton.id = "uets-toggle-ui-button"; updateToggleButtonAppearance(); // Tap/click counter for config GUI let lastTapTime = 0; let tapCount = 0; const TAP_WINDOW_MS = 500; const handleTap = () => { const now = Date.now(); if (now - lastTapTime < TAP_WINDOW_MS) { tapCount += 1; } else { tapCount = 1; } lastTapTime = now; if (tapCount === 4) { createConfigGui(); tapCount = 0; } else if (tapCount === 1) { handleToggleUiClick(); } }; sharedState.toggleButton.addEventListener("click", handleTap); sharedState.toggleButton.addEventListener("touchend", handleTap); document.body.appendChild(sharedState.toggleButton); }; // === DOMAIN-SPECIFIC MODULES === // KAHOOT MODULE const kahootModule = { QUIZ_TYPES: ['quiz', 'multiple_select_quiz'], CONTROLLER_CHANNEL: '/service/controller', COLORS: ['red', 'blue', 'yellow', 'green'], loadSocketIO: () => { return new Promise((resolve, reject) => { if (window.io) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://cdn.socket.io/4.8.1/socket.io.min.js'; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Socket.IO')); document.head.appendChild(script); }); }, isCometDEndpoint: url => url.includes('/cometd/') && url.includes('kahoot.it'), parseJSON: data => { try { return typeof data === 'string' ? JSON.parse(data) : data; } catch { return null; } }, extractQuizData: content => { const parsed = kahootModule.parseJSON(content); return parsed && kahootModule.QUIZ_TYPES.includes(parsed.type) && 'choice' in parsed ? parsed : null; }, containsQuizAnswer: data => { const parsed = kahootModule.parseJSON(data); return Array.isArray(parsed) && parsed.some(item => item.channel === kahootModule.CONTROLLER_CHANNEL && item.data?.content && kahootModule.extractQuizData(item.data.content) ); }, connectToServer: async () => { if (!sharedState.kahootClientId || !sharedState.kahootGameId) return; // Better connection state checking if (sharedState.kahootSocket) { if (sharedState.kahootSocket.connected) { GM_log("[*] Already connected to UETS-server"); return; } else { // Clean up existing socket sharedState.kahootSocket.disconnect(); sharedState.kahootSocket = null; } } if (sharedState.kahootHasConnected) { GM_log("[*] Connection already established to UETS-server"); return; } try { await kahootModule.loadSocketIO(); GM_log(`[*] Connecting to UETS-server with clientId: ${sharedState.kahootClientId}, gameId: ${sharedState.kahootGameId}`); sharedState.kahootSocket = io(sharedState.config.serverUrl, { transports: ['websocket', 'polling'], forceNew: true, // Force a new connection timeout: 10000 }); sharedState.kahootSocket.on('connect', () => { GM_log("[+] Connected to UETS-server"); sharedState.kahootHasConnected = true; sharedState.kahootSocket.emit('identify', { clientId: sharedState.kahootClientId, gameId: sharedState.kahootGameId }); }); sharedState.kahootSocket.on('answer_counts', (data) => { GM_log("[*] Received answer counts:", data); sharedState.kahootCurrentQuestion = data.questionIndex; sharedState.kahootAnswerCounts = data.counts; kahootModule.updateAnswerIndicators(); }); sharedState.kahootSocket.on('question_reset', (data) => { GM_log("[*] Question reset:", data); if (data.questionIndex === sharedState.kahootCurrentQuestion) { sharedState.kahootAnswerCounts = {}; kahootModule.updateAnswerIndicators(); } }); sharedState.kahootSocket.on('error', (error) => { GM_log("[!] Kahoot server error:", error); }); sharedState.kahootSocket.on('disconnect', () => { GM_log("[!] Kahoot server connection closed"); sharedState.kahootHasConnected = false; sharedState.kahootSocket = null; }); } catch (error) { GM_log("[!] Failed to connect to UETS-server:", error); sharedState.kahootHasConnected = false; // Reduce retry frequency setTimeout(() => { if (!sharedState.kahootHasConnected && sharedState.kahootClientId && sharedState.kahootGameId) { kahootModule.connectToServer(); } }, 5000); // Increased from 3000 to 5000 } }, sendAnswerToServer: (questionIndex, choices) => { GM_log(`[*] Sending Kahoot answer: Q${questionIndex} = ${choices}`); sharedState.kahootSocket.emit('answer', { questionIndex: questionIndex, choices: choices, clientId: sharedState.kahootClientId, gameId: sharedState.kahootGameId }); }, updateAnswerIndicators: () => { if (window.location.pathname !== "/gameblock") return; const buttons = document.querySelectorAll('[data-functional-selector^="answer-"]'); buttons.forEach((button, index) => { const existingIndicator = button.querySelector('.kahoot-answer-indicator'); if (existingIndicator) { existingIndicator.remove(); } button.classList.add('kahoot-answer-button'); const count = sharedState.kahootAnswerCounts[index] || 0; if (count > 0) { const indicator = document.createElement('div'); indicator.className = 'kahoot-answer-indicator'; indicator.textContent = count; indicator.style.backgroundColor = kahootModule.getColorForChoice(index); button.appendChild(indicator); } }); }, getColorForChoice: (index) => { const colorMap = { 0: '#ff0000', // red 1: '#0066cc', // blue 2: '#ffcc00', // yellow 3: '#00cc00' // green }; return colorMap[index] || '#666666'; }, logQuizAnswer: (data, direction = "SEND") => { const parsed = kahootModule.parseJSON(data); if (!Array.isArray(parsed)) return; parsed.forEach(item => { if (item.channel !== kahootModule.CONTROLLER_CHANNEL || !item.data?.content) return; const quizData = kahootModule.extractQuizData(item.data.content); if (!quizData) return; const isMultiple = quizData.type === 'multiple_select_quiz'; const choices = Array.isArray(quizData.choice) ? quizData.choice : [quizData.choice]; const choiceStr = choices.length > 1 ? `[${choices.join(',')}]` : choices[0]; GM_log(`[*] ${isMultiple ? 'MULTI' : 'SINGLE'} Q${quizData.questionIndex} = ${choiceStr} (${direction})`); if (direction === "SEND") { kahootModule.sendAnswerToServer(quizData.questionIndex, choices); } }); }, extractIdentifiers: (data) => { const parsed = kahootModule.parseJSON(data); if (!Array.isArray(parsed)) return; let shouldConnect = false; parsed.forEach(item => { if (item.clientId && !sharedState.kahootClientId) { sharedState.kahootClientId = item.clientId; GM_log(`[*] Kahoot Client ID: ${sharedState.kahootClientId}`); shouldConnect = true; } if (item.data && item.data.gameid && !sharedState.kahootGameId) { sharedState.kahootGameId = item.data.gameid; GM_log(`[*] Kahoot Game ID: ${sharedState.kahootGameId}`); shouldConnect = true; } }); // Only connect once when we have both IDs and haven't connected yet if (shouldConnect && sharedState.kahootClientId && sharedState.kahootGameId && !sharedState.kahootHasConnected && !sharedState.kahootSocket) { kahootModule.connectToServer(); } }, injectScript: () => { const script = document.createElement('script'); script.textContent = ` (() => { const NativeWebSocket = window.WebSocket; const QUIZ_TYPES = ['quiz', 'multiple_select_quiz']; const CONTROLLER_CHANNEL = '/service/controller'; const isCometDEndpoint = url => url.includes('/cometd/') && url.includes('kahoot.it'); const parseJSON = data => { try { return typeof data === 'string' ? JSON.parse(data) : data; } catch { return null; } }; const extractQuizData = content => { const parsed = parseJSON(content); return parsed && QUIZ_TYPES.includes(parsed.type) && 'choice' in parsed ? parsed : null; }; const containsQuizAnswer = data => { const parsed = parseJSON(data); return Array.isArray(parsed) && parsed.some(item => item.channel === CONTROLLER_CHANNEL && item.data?.content && extractQuizData(item.data.content)); }; const logQuizAnswer = (data, direction) => { const parsed = parseJSON(data); if (!Array.isArray(parsed)) return; parsed.forEach(item => { if (item.channel !== CONTROLLER_CHANNEL || !item.data?.content) return; const quizData = extractQuizData(item.data.content); if (!quizData) return; const isMultiple = quizData.type === 'multiple_select_quiz'; const choices = Array.isArray(quizData.choice) ? quizData.choice : [quizData.choice]; const choiceStr = choices.length > 1 ? \`[\${choices.join(',')}]\` : choices[0]; console.log(\`[*] \${isMultiple ? 'MULTI' : 'SINGLE'} CHOICE Q\${quizData.questionIndex} = \${choiceStr} (\${direction})\`); window.dispatchEvent(new CustomEvent('kahootAnswer', { detail: { data, direction, quizData, choices } })); }); }; const extractIdentifiers = (data) => { window.dispatchEvent(new CustomEvent('kahootData', { detail: data })); }; window.WebSocket = function(url, protocols) { const ws = new NativeWebSocket(url, protocols); if (!isCometDEndpoint(url)) return ws; console.log("[+] CometD WebSocket created"); return new Proxy(ws, { get(target, prop) { const value = Reflect.get(target, prop); if (prop === 'send' && typeof value === 'function') { return function(...args) { extractIdentifiers(args[0]); if (containsQuizAnswer(args[0])) logQuizAnswer(args[0], "SEND"); return value.apply(target, args); }; } if (prop === 'addEventListener' && typeof value === 'function') { return function(type, listener, options) { const wrappedListener = type === 'message' ? function(event) { extractIdentifiers(event.data); if (containsQuizAnswer(event.data)) logQuizAnswer(event.data, "RECEIVE"); return listener?.call(this, event); } : listener; return value.call(target, type, wrappedListener, options); }; } return value; }, set(target, prop, value) { if (prop === 'onmessage' && typeof value === 'function') { const wrappedHandler = function(event) { extractIdentifiers(event.data); if (containsQuizAnswer(event.data)) logQuizAnswer(event.data, "RECEIVE"); return value.call(this, event); }; return Reflect.set(target, prop, wrappedHandler); } return Reflect.set(target, prop, value); } }); }; Object.setPrototypeOf(window.WebSocket, NativeWebSocket); Object.defineProperty(window.WebSocket, 'prototype', { value: NativeWebSocket.prototype }); ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'].forEach(prop => { window.WebSocket[prop] = NativeWebSocket[prop]; }); console.log("[+] Kahoot quiz answer interceptor active"); })(); (document.head || document.documentElement).appendChild(script); script.remove(); `; (document.head || document.documentElement).appendChild(script); script.remove(); `; (document.head || document.documentElement).appendChild(script); script.remove(); `; }, setupHTTPInterceptors: () => { const originalXHRSend = XMLHttpRequest.prototype.send; const kahootXHRSendHandler = function (data) { if (kahootModule.isCometDEndpoint(this._interceptedUrl)) { kahootModule.extractIdentifiers(data); if (kahootModule.containsQuizAnswer(data)) { kahootModule.logQuizAnswer(data, "XHR SEND"); } } return originalXHRSend.call(this, data); }; const originalXHROpen = XMLHttpRequest.prototype.open; const kahootXHROpenHandler = function (method, url, ...args) { this._interceptedUrl = url; return originalXHROpen.call(this, method, url, ...args); }; if (!XMLHttpRequest.prototype.send._kahootPatched) { XMLHttpRequest.prototype.send = kahootXHRSendHandler; XMLHttpRequest.prototype.send._kahootPatched = true; } if (!XMLHttpRequest.prototype.open._kahootPatched) { XMLHttpRequest.prototype.open = kahootXHROpenHandler; XMLHttpRequest.prototype.open._kahootPatched = true; } const kahootOriginalFetch = window.fetch; if (!window.fetch._kahootPatched) { window.fetch = function (input, init) { const url = typeof input === 'string' ? input : input.url; if (kahootModule.isCometDEndpoint(url) && init?.body) { kahootModule.extractIdentifiers(init.body); if (kahootModule.containsQuizAnswer(init.body)) { kahootModule.logQuizAnswer(init.body, "FETCH SEND"); } } return kahootOriginalFetch.apply(this, arguments); }; window.fetch._kahootPatched = true; } }, initialize: () => { kahootModule.injectScript(); kahootModule.setupHTTPInterceptors(); // Listen for events from injected script window.addEventListener('kahootData', (event) => { kahootModule.extractIdentifiers(event.detail); }); window.addEventListener('kahootAnswer', (event) => { const { direction, quizData, choices } = event.detail; if (direction === "SEND") { kahootModule.sendAnswerToServer(quizData.questionIndex, choices); } }); // Periodically update indicators setInterval(() => { kahootModule.updateAnswerIndicators(); }, 200); } }; // WAYGROUND MODULE const waygroundModule = { selectors: { questionContainer: 'div[data-testid="question-container-text"]', questionText: 'div[data-testid="question-container-text"] .question-text-color', questionImage: 'div[data-testid="question-container-text"] img, div[class*="question-media-container"] img, img[data-testid="question-container-image"], .question-image', optionButtons: "button.option", optionText: "div#optionText div.resizeable, .option-text div.resizeable, div.resizeable", pageInfo: 'div.pill p, div[class*="question-counter"] p', quizContainer: "div[data-quesid]", }, lastPageInfo: "INITIAL_STATE", debounce: (func, wait) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; }, getCurrentQuestionId: () => { const quizContainer = document.querySelector( waygroundModule.selectors.quizContainer, ); return quizContainer ? quizContainer.getAttribute("data-quesid") : null; }, extractAndProcess: async () => { if (!sharedState.uiModificationsEnabled) return; document .querySelectorAll( ".uets-ddg-link, .uets-gemini-button, .uets-copy-prompt-button, .uets-get-answer-button, .uets-main-question-buttons-container, .uets-streak-bonus", ) .forEach((el) => el.remove()); document.querySelectorAll(".uets-option-wrapper").forEach((wrapper) => { const button = wrapper.querySelector("button.option"); if (button && wrapper.parentNode) { wrapper.parentNode.insertBefore(button, wrapper); } wrapper.remove(); }); sharedState.elementsToCleanup = sharedState.elementsToCleanup.filter( (el) => { return el && el.parentNode; }, ); const currentQuestionId = waygroundModule.getCurrentQuestionId(); if (currentQuestionId !== sharedState.currentQuestionId) { sharedState.currentQuestionId = currentQuestionId; if (currentQuestionId && sharedState.quizData[currentQuestionId]) { const questionData = sharedState.quizData[currentQuestionId]; const response = await sendQuestionToServer( currentQuestionId, questionData.type, questionData.structure.options ? questionData.structure.options.map((opt) => opt.id) : [], ); if (response && response.hasAnswer) { highlightCorrectAnswers( response.correctAnswers, response.questionType, ); } } } let questionTitle = ""; let optionTexts = []; let questionImageUrl = null; const questionImageElement = document.querySelector( waygroundModule.selectors.questionImage, ); if (questionImageElement?.src) { questionImageUrl = questionImageElement.src.startsWith("/") ? window.location.origin + questionImageElement.src : questionImageElement.src; } const questionTitleTextElement = document.querySelector( waygroundModule.selectors.questionText, ); const questionTextOuterContainer = document.querySelector( waygroundModule.selectors.questionContainer, ); // Extract options first const optionButtons = document.querySelectorAll( waygroundModule.selectors.optionButtons, ); optionButtons.forEach((button) => { const text = button .querySelector(waygroundModule.selectors.optionText) ?.textContent.trim(); if (text) optionTexts.push(text); }); if (questionTitleTextElement && questionTextOuterContainer) { questionTitle = questionTitleTextElement.textContent.trim(); if (questionTitle || questionImageUrl || optionTexts.length > 0) { addQuestionButtons( questionTextOuterContainer, questionTitle, optionTexts, questionImageUrl, "quiz", true, "DDG", ); } } }, checkPageInfoAndReprocess: () => { const pageInfoElement = document.querySelector( waygroundModule.selectors.pageInfo, ); let currentPageInfoText = pageInfoElement ? pageInfoElement.textContent.trim() : ""; if (currentPageInfoText !== waygroundModule.lastPageInfo) { waygroundModule.lastPageInfo = currentPageInfoText; waygroundModule.extractAndProcess(); } }, enhanceStreakCounter: () => { const streakSpan = document.querySelector( 'span[data-testid="streak-pill-level"]', ); if ( streakSpan && !streakSpan.nextSibling?.classList?.contains("uets-streak-bonus") ) { const bonusSpan = document.createElement("span"); bonusSpan.classList.add("uets-streak-bonus"); streakSpan.parentNode.insertBefore(bonusSpan, streakSpan.nextSibling); const updateBonus = () => { const level = parseInt(streakSpan.textContent.trim()) || 0; let bonus = 0; if (level >= 1 && level <= 3) bonus = 100; else if (level >= 4 && level <= 6) bonus = 200; else if (level >= 7) bonus = 300; bonusSpan.textContent = `+${bonus}`; }; updateBonus(); // Observe changes to the streak span's text const observer = new MutationObserver(updateBonus); observer.observe(streakSpan, { childList: true, subtree: true, characterData: true, }); } }, modifyTabLeaveWarning: () => { const ruleDiv = document.querySelector('.test-mode-container'); if (ruleDiv) { if (sharedState.originalTabLeaveHTML === null) { sharedState.originalTabLeaveHTML = ruleDiv.innerHTML; } ruleDiv.innerHTML = "<h4 data-v-aceb3d94=\"\" class=\"heading\">Teacher rules bypassed: </h4><div data-v-aceb3d94=\"\" class=\"test-mode-rules\"><!----><div data-v-aceb3d94=\"\" class=\"rule\">You can leave the Wayground tab during this session. To remove fullscreen warnings enable fullscreen spoofing in settings.</div></div>"; } }, modifyStartButton: () => { const startButton = document.querySelector('.start-game'); if (startButton) { const span = startButton.querySelector('span'); if (span && span.textContent.includes('Start in fullscreen mode')) { if (sharedState.originalStartButtonText === null) { sharedState.originalStartButtonText = span.textContent; } span.textContent = 'Start game'; } } }, initialize: () => { waygroundModule.lastPageInfo = "INITIAL_STATE"; if (sharedState.observer) sharedState.observer.disconnect(); sharedState.observer = new MutationObserver( waygroundModule.debounce(() => { if (sharedState.uiModificationsEnabled) { waygroundModule.checkPageInfoAndReprocess(); waygroundModule.enhanceStreakCounter(); waygroundModule.modifyTabLeaveWarning(); waygroundModule.modifyStartButton(); } }, 500), ); sharedState.observer.observe(document.body, { childList: true, subtree: true, }); if (sharedState.uiModificationsEnabled) { waygroundModule.extractAndProcess(); waygroundModule.enhanceStreakCounter(); waygroundModule.modifyTabLeaveWarning(); waygroundModule.modifyStartButton(); // Add delay to check for existing streak element // NOTE: this may not be enough on shitty connections setTimeout(() => waygroundModule.enhanceStreakCounter(), 1000); } }, }; // TESTPORTAL MODULE const testportalModule = { customRegExpTestFunction: function (s) { const string = this.toString(); if (string.includes("native code") && string.includes("function")) { return true; } return sharedState.originalRegExpTest.call(this, s); }, processQuestionElement: (qEssenceEl) => { if ( !sharedState.uiModificationsEnabled || qEssenceEl.dataset.enhancementsAdded ) return; let questionTextContent = ( qEssenceEl.innerText || qEssenceEl.textContent || "" ).trim(); if (!questionTextContent) return; const questionContainer = qEssenceEl.closest( ".question_container_wrapper, .question-view, .question-content, form, div.row, .question_row_content_container, .question_item_view_v2, .q_tresc_pytania_mock, .question_essence_fs", ); let answerElements = []; if (questionContainer) { answerElements = Array.from( questionContainer.querySelectorAll( ".answer_body, .answer-body, .odpowiedz_tresc", ), ); } else { const formElement = qEssenceEl.closest("form"); if (formElement) { answerElements = Array.from( formElement.querySelectorAll( ".answer_body, .answer-body, .odpowiedz_tresc", ), ); } } const options = answerElements .map((optEl) => (optEl.innerText || optEl.textContent || "").trim()) .filter(Boolean); let questionImageElement = qEssenceEl.querySelector("img"); if (!questionImageElement && questionContainer) { questionImageElement = questionContainer.querySelector( "img.question-image, img.question_image_preview, .question_media img, .question-body__attachment img, .image_area img", ); } const imageUrl = questionImageElement ? questionImageElement.src : null; if (questionTextContent || imageUrl || options.length > 0) { addQuestionButtons( qEssenceEl, questionTextContent, options, imageUrl, "test", false, "DDG", ); } qEssenceEl.dataset.enhancementsAdded = "true"; }, enhanceAllExistingQuestions: () => { if (!sharedState.uiModificationsEnabled) return; const qElements = document.getElementsByClassName("question_essence"); for (const qEl of qElements) { testportalModule.processQuestionElement(qEl); } }, initialize: () => { if (sharedState.uiModificationsEnabled) { RegExp.prototype.test = testportalModule.customRegExpTestFunction; testportalModule.enhanceAllExistingQuestions(); if (sharedState.observer) sharedState.observer.disconnect(); sharedState.observer = new MutationObserver((mutationsList) => { if (!sharedState.uiModificationsEnabled) return; for (const mutation of mutationsList) { if (mutation.type === "childList") { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if ( node.classList && (node.classList.contains("question_essence") || node.querySelector(".question_essence")) ) { if (node.classList.contains("question_essence")) { testportalModule.processQuestionElement(node); } else { const qElements = node.getElementsByClassName("question_essence"); for (const qEl of qElements) { testportalModule.processQuestionElement(qEl); } } } } } } } }); sharedState.observer.observe(document.body, { childList: true, subtree: true, }); } else { RegExp.prototype.test = sharedState.originalRegExpTest; } }, }; // GOOGLE FORMS MODULE const googleFormsModule = { handleCopyClick: (event) => { event.preventDefault(); const button = event.target; const questionBlock = button.closest("div[jsmodel]"); if (!questionBlock) return; const questionTitleEl = questionBlock.querySelector( 'div[role="heading"] > span:first-child', ); const questionTitle = questionTitleEl ? questionTitleEl.textContent.trim() : "Question not found"; let promptText = ""; let questionType = "Unknown Type"; let options = []; const radioOptions = questionBlock.querySelectorAll('div[role="radio"]'); if (radioOptions.length > 0) { questionType = "Single Choice"; radioOptions.forEach((radio) => { const label = radio.getAttribute("aria-label"); if (label) options.push(`- ${label}`); }); } else { const checkOptions = questionBlock.querySelectorAll( 'div[role="checkbox"]', ); if (checkOptions.length > 0) { questionType = "Multi Choice (Select all that apply)"; checkOptions.forEach((check) => { const label = check.getAttribute("aria-label") || check.getAttribute("data-answer-value"); if (label) options.push(`- ${label}`); }); } else { const textInput = questionBlock.querySelector( 'div[role="textbox"], textarea, input[type="text"]', ); if (textInput) { questionType = "Free Text"; } } } promptText = `[${questionType}]\nQuestion: ${questionTitle}`; if (options.length > 0) { promptText += "\n\nOptions:\n" + options.join("\n"); } const instructionText = "Reply with the correct answer and a quick reasoning why it is the correct answer. Don't over-complicate stuff."; promptText += `\n\n${instructionText}`; GM_setClipboard(promptText); const originalText = button.textContent; button.textContent = "Copied!"; button.disabled = true; setTimeout(() => { button.textContent = originalText; button.disabled = false; }, 1500); }, handleAiClick: async (event) => { event.preventDefault(); const button = event.target; const questionBlock = button.closest("div[jsmodel]"); if (!questionBlock) return; const questionTitleEl = questionBlock.querySelector( 'div[role="heading"] > span:first-child', ); const questionTitle = questionTitleEl ? questionTitleEl.textContent.trim() : "Question not found"; let options = []; const radioOptions = questionBlock.querySelectorAll('div[role="radio"]'); if (radioOptions.length > 0) { radioOptions.forEach((radio) => { const label = radio.getAttribute("aria-label"); if (label) options.push(label); }); } else { const checkOptions = questionBlock.querySelectorAll( 'div[role="checkbox"]', ); if (checkOptions.length > 0) { checkOptions.forEach((check) => { const label = check.getAttribute("aria-label") || check.getAttribute("data-answer-value"); if (label) options.push(label); }); } } askGemini(questionTitle, options, null, "form"); }, handleDdgClick: (event) => { event.preventDefault(); const button = event.target; const questionBlock = button.closest("div[jsmodel]"); if (!questionBlock) return; const questionTitleEl = questionBlock.querySelector( 'div[role="heading"] > span:first-child', ); const questionTitle = questionTitleEl ? questionTitleEl.textContent.trim() : "Question not found"; window.open( `https://duckduckgo.com/?q=${encodeURIComponent(questionTitle)}`, "_blank", ); }, addButtons: () => { if (!sharedState.uiModificationsEnabled) return; const questionBlocks = document.querySelectorAll( 'div[role="listitem"] > div[jsmodel]', ); questionBlocks.forEach((block) => { if (block.dataset.uetsButtonsAdded) return; block.dataset.uetsButtonsAdded = "true"; // Find the heading container (the question text/title) const headingContainer = block.querySelector('div[role="heading"]'); if (!headingContainer) return; // Extract question text const questionTitleEl = headingContainer.querySelector('span'); const questionText = questionTitleEl ? questionTitleEl.textContent.trim() : "Question not found"; // Extract options let options = []; const radioOptions = block.querySelectorAll('div[role="radio"]'); if (radioOptions.length > 0) { radioOptions.forEach((radio) => { const label = radio.getAttribute("aria-label"); if (label) options.push(label); }); } else { const checkOptions = block.querySelectorAll('div[role="checkbox"]'); if (checkOptions.length > 0) { checkOptions.forEach((check) => { const label = check.getAttribute("aria-label") || check.getAttribute("data-answer-value"); if (label) options.push(label); }); } } // Extract image URL if present (assuming images are in the block) let imageUrl = null; const imageElement = block.querySelector('img'); if (imageElement && imageElement.src) { imageUrl = imageElement.src.startsWith("/") ? window.location.origin + imageElement.src : imageElement.src; } // Use addQuestionButtons to add the buttons const buttonsContainer = document.createElement("div"); buttonsContainer.classList.add("uets-main-question-buttons-container"); addQuestionButtons( buttonsContainer, questionText, options, imageUrl, "form", false, "DDG", ); // Insert the buttonsContainer directly after the headingContainer if (headingContainer.parentNode === block) { block.insertBefore(buttonsContainer, headingContainer.nextSibling); } else { headingContainer.parentNode.insertBefore(buttonsContainer, headingContainer.nextSibling); } sharedState.elementsToCleanup.push(buttonsContainer); }); }, initialize: () => { if (sharedState.observer) sharedState.observer.disconnect(); sharedState.observer = new MutationObserver(() => { googleFormsModule.addButtons(); }); sharedState.observer.observe(document.body, { childList: true, subtree: true, }); googleFormsModule.addButtons(); }, }; // === SHARED QUIZ DATA PROCESSOR === const processQuizData = (data) => { GM_log("[*] Trying to get all questions..."); try { var questionKeys = Object.keys(data.data.room.questions); } catch (e) { var questionKeys = Object.keys(data.room.questions); } for (const questionKey of questionKeys) { GM_log("[*] ----------------"); try { var questionData = data.data.room.questions[questionKey]; } catch (e) { var questionData = data.room.questions[questionKey]; } sharedState.quizData[questionKey] = questionData; // Store the complete question data in questionsPool sharedState.questionsPool[questionKey] = questionData; GM_log(`[+] Question ID: ${questionKey}`); GM_log(`[+] Question Type: ${questionData.type}`); GM_log(`[+] Question Text: ${questionData.structure.query.text}`); if (questionData.structure.query.media) { for (const media of questionData.structure.query.media) { GM_log(`[+] Media URL: ${media.url} (Type: ${media.type})`); } } const options = questionData.structure.options || []; for (const option of options) { GM_log(`[+] Option: ${option.text} (${option.id})`); if (option.media) { for (const media of option.media) { GM_log(`[+] Media URL: ${media.url} (Type: ${media.type})`); } } } } }; // === REQUEST INTERCEPTION === const originalXMLHttpRequestOpen = XMLHttpRequest.prototype.open; const originalXMLHttpRequestSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, ...args) { this._method = method; this._url = url; if ( typeof url === "string" && (url.includes("play-api/createTestGameActivity") && (url.includes("wayground.com") || url.includes("quizizz.com"))) ) { this._blocked = true; GM_log("[+] Blocked cheating detection request to createTestGameActivity"); } if ( typeof url === "string" && (url.includes("play-api") && (url.includes("soloJoin") || url.includes("rejoinGame") || url.includes("join")) && (url.includes("wayground.com") || url.includes("quizizz.com"))) ) { this.addEventListener("load", function () { if (this.status === 200) { try { const data = JSON.parse(this.responseText); processQuizData(data); } catch (e) { GM_log("[!] Failed to parse response:", e); } } }); } if ( typeof url === "string" && (url.includes("play-api") && (url.includes("proceedGame") || url.includes("soloProceed")) && (url.includes("wayground.com") || url.includes("quizizz.com"))) ) { this.addEventListener("load", function () { if (this.status === 200) { try { const data = JSON.parse(this.responseText); processProceedGameResponse(data); } catch (e) { GM_log("[!] Failed to parse proceedGame response:", e); } } }); } return originalXMLHttpRequestOpen.call(this, method, url, ...args); }; XMLHttpRequest.prototype.send = function (data) { // Block cheating detection requests if (this._blocked) { GM_log("[+] Cheating detection request blocked - not sending data"); return; } // Intercept POST requests to proceedGame and modify timeTaken if ( this._method === "POST" && this._url && (this._url.includes("play-api") && (this._url.includes("proceedGame") || this._url.includes("soloProceed")) && (this._url.includes("wayground.com") || this._url.includes("quizizz.com"))) ) { if (data) { try { let requestData = JSON.parse(data); requestData = processProceedGameRequest(requestData); const modifiedData = JSON.stringify(requestData); return originalXMLHttpRequestSend.call(this, modifiedData); } catch (e) { GM_log("[!] Failed to parse/modify proceedGame request:", e); return originalXMLHttpRequestSend.call(this, data); } } } // Intercept requests to reaction-update and resend twice if ( this._method === "POST" && this._url && (this._url.includes("wayground.com") || this._url.includes("quizizz.com")) && this._url.includes("_gameapi/main/public/v1/games/",) && this._url.includes("/reaction-update") ) { // Send original request const result = originalXMLHttpRequestSend.call(this, data); // Resend based on config if (sharedState.config.enableReactionSpam) { for (let i = 1; i <= sharedState.config.reactionSpamCount; i++) { setTimeout(() => { const xhr = new XMLHttpRequest(); xhr.open(this._method, this._url); xhr.send(data); }, sharedState.config.reactionSpamDelay * i); } } return result; } return originalXMLHttpRequestSend.call(this, data); }; const originalFetch = window.fetch; window.fetch = function (url, options) { // Block cheating detection requests if ( typeof url === "string" && (url.includes("play-api/createTestGameActivity") && (url.includes("wayground.com") || url.includes("quizizz.com"))) ) { GM_log("[+] Blocked cheating detection request to createTestGameActivity"); return Promise.resolve( new Response(JSON.stringify({}), { status: 200, statusText: "OK" }), ); } // Intercept POST requests to proceedGame via fetch if ( typeof url === "string" && (url.includes("play-api") && (url.includes("proceedGame") || url.includes("soloProceed")) && (url.includes("wayground.com") || url.includes("quizizz.com"))) && options && options.method === "POST" && options.body ) { try { let requestData = JSON.parse(options.body); requestData = processProceedGameRequest(requestData); const modifiedOptions = { ...options, body: JSON.stringify(requestData), }; return originalFetch.call(this, url, modifiedOptions); } catch (e) { GM_log("[!] Failed to parse/modify proceedGame fetch request:", e); } } // Intercept requests to reaction-update via fetch if ( typeof url === "string" && (url.includes("wayground.com") || url.includes("quizizz.com")) && url.includes("_gameapi/main/public/v1/games/") && url.includes("/reaction-update") && options && options.method === "POST" && options.body && sharedState.config.enableReactionSpam ) { // Send original request const result = originalFetch.call(this, url, options); // Resend based on config for (let i = 1; i <= sharedState.config.reactionSpamCount; i++) { setTimeout(() => { originalFetch.call(this, url, options); }, sharedState.config.reactionSpamDelay * i); } return result; } if ( typeof url === "string" && (url.includes("play-api") && (url.includes("soloJoin") || url.includes("rejoinGame") || url.includes("join")) && (url.includes("wayground.com") || url.includes("quizizz.com"))) ) { return originalFetch.call(this, url, options).then((response) => { if (response.ok) { return response .clone() .json() .then((data) => { processQuizData(data); return response; }) .catch(() => response); } return response; }); } if ( typeof url === "string" && (url.includes("play-api") && (url.includes("proceedGame") || url.includes("soloProceed")) && (url.includes("wayground.com") || url.includes("quizizz.com"))) ) { return originalFetch.call(this, url, options).then((response) => { if (response.ok) { return response .clone() .json() .then((data) => { processProceedGameResponse(data); return response; }) .catch(() => response); } return response; }); } return originalFetch.call(this, url, options); }; // === DOMAIN DETECTION AND INITIALIZATION === const initializeDomainSpecific = () => { const hostname = window.location.hostname; if (hostname.includes("kahoot.it")) { GM_log("[*] Initializing Kahoot module..."); kahootModule.initialize(); } else if ( hostname.includes("quizizz.com") || hostname.includes("wayground.com") ) { GM_log("[*] Initializing Wayground module..."); if (sharedState.config.enableSpoofFullscreen) { spoofFullscreenAndFocus(); } waygroundModule.initialize(); } else if ( hostname.includes("testportal.net") || hostname.includes("testportal.pl") ) { GM_log("[*] Initializing Testportal module..."); if (sharedState.config.enableSpoofFullscreen) { spoofFullscreenAndFocus(); } testportalModule.initialize(); } else if ( hostname.includes("docs.google.com") && window.location.pathname.includes("/forms/") ) { GM_log("[*] Initializing Google Forms module..."); googleFormsModule.initialize(); } }; // === MAIN INITIALIZATION === const main = () => { // Load config on startup const savedConfig = GM_getValue(CONFIG_STORAGE_KEY, null); if (savedConfig) { sharedState.config = { ...DEFAULT_CONFIG, ...savedConfig }; } // Sync API key from old storage if config doesn't have it if (!sharedState.config.geminiApiKey) { sharedState.config.geminiApiKey = GM_getValue(GEMINI_API_KEY_STORAGE, ""); } createToggleButton(); initializeDomainSpecific(); GM_log(`[+] UETS loaded on ${sharedState.currentDomain}`); GM_log(`[+] Made by Nyx (with the slight help of GH Copilot)`); // Check for first run and show welcome popup if (!GM_getValue(sharedState.firstRunKey, false)) { GM_setValue(sharedState.firstRunKey, true); setTimeout(() => showWelcomePopup(), 1000); // Delay to ensure page is loaded } }; if (document.body) { main(); } else { window.addEventListener("DOMContentLoaded", main); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址