// ==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);
}
})();