// ==UserScript==
// @name Quizlet Live HACK MOD MENU 2025
// @namespace http://tampermonkey.net/
// @version 2.3.1
// @description A hack for Quizlet Live updated on April 2025! - Dark UI Theme with Background Image - UI loads instantly. Now with working cheat logic.
// @author Dark Shadow
// @match https://quizlet.com/live/*
// @match https://quizlet.com/live
// @icon https://www.google.com/s2/favicons?sz=64&domain=quizlet.com
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- Configuration & State ---
var UIVersion = '2.3'; // Version der UI-Design von Script 2
var LogicVersion = '1.0.7'; // Version der Kernlogik von Script 1
var autoAnswer = false;
var showAnswers = false;
var pairs = [];
var lastAnswer = ""; // Wird von der Logik aus Script 1 verwendet
var POLLING_INTERVAL_MS = 100; // Beibehalten von Script 2, 1ms von Script 1 ist zu aggressiv
// --- UI Creation (Compact & Revised Animation) - AUS SCRIPT 2 ---
function createUI() {
if (document.querySelector('.ql-hack-main-ui')) {
console.warn("[QuizletHack] UI already exists.");
return;
}
const uiElement = document.createElement('div');
uiElement.className = 'ql-hack-main-ui';
uiElement.style.position = 'fixed';
uiElement.style.top = '20px';
uiElement.style.left = '20px';
uiElement.style.width = '260px';
uiElement.style.backgroundImage = 'url("https://c4.wallpaperflare.com/wallpaper/945/873/190/dark-black-shadows-mysterious-wallpaper-preview.jpg")';
uiElement.style.backgroundSize = 'cover';
uiElement.style.backgroundPosition = 'center center';
uiElement.style.backgroundRepeat = 'no-repeat';
uiElement.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
uiElement.style.borderRadius = '8px';
uiElement.style.boxShadow = '0px 3px 10px rgba(0, 0, 0, 0.6)';
uiElement.style.zIndex = '9999';
uiElement.style.overflow = 'hidden';
uiElement.style.fontFamily = 'Arial, sans-serif';
uiElement.style.color = 'white';
uiElement.style.backdropFilter = 'blur(3px)';
const handle = document.createElement('div');
handle.className = 'ql-hack-handle';
handle.style.fontSize = '14px';
handle.textContent = 'Quizlet Utils';
handle.style.color = 'white';
handle.style.width = '100%';
handle.style.boxSizing = 'border-box';
handle.style.height = '30px';
handle.style.backgroundColor = '#111111';
handle.style.cursor = 'grab';
handle.style.textAlign = 'center';
handle.style.lineHeight = '30px';
handle.style.position = 'relative';
handle.style.borderTopLeftRadius = '8px';
handle.style.borderTopRightRadius = '8px';
handle.style.transition = 'border-radius 0.3s ease-in-out';
uiElement.appendChild(handle);
const createButton = (text, bgColor, rightPos, clickHandler) => {
const button = document.createElement('div');
button.textContent = text;
button.style.position = 'absolute';
button.style.top = '0';
button.style.right = rightPos;
button.style.width = '30px';
button.style.height = '100%';
button.style.backgroundColor = bgColor;
button.style.color = 'white';
button.style.display = 'flex';
button.style.justifyContent = 'center';
button.style.alignItems = 'center';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.addEventListener('click', clickHandler);
handle.appendChild(button);
return button;
};
const closeButton = createButton('✕', '#444444', '0px', () => {
if (uiElement && uiElement.parentNode) uiElement.parentNode.removeChild(uiElement);
autoAnswer = false; showAnswers = false;
});
closeButton.style.borderTopRightRadius = '8px';
const minimizeButton = createButton('─', '#444444', '30px', () => {
toggleMinimize();
});
const contentContainer = document.createElement('div');
contentContainer.className = 'ql-hack-content';
uiElement.appendChild(contentContainer);
const header3 = document.createElement('h2');
header3.textContent = 'Answering';
header3.style.marginTop = '0px'; header3.style.marginBottom = '10px';
header3.style.textAlign = 'center'; header3.style.fontSize = '16px';
header3.style.color = 'white';
header3.style.fontWeight = 'bold';
contentContainer.appendChild(header3);
const createSwitch = (labelText, onChangeCallback) => {
const container = document.createElement('div');
container.style.display = 'flex'; container.style.alignItems = 'center';
container.style.justifyContent = 'space-between'; container.style.marginBottom = '12px';
const label = document.createElement('span');
label.textContent = labelText; label.style.fontSize = '13px'; label.style.color = 'white';
container.appendChild(label);
const switchLabel = document.createElement('label'); switchLabel.className = 'ql-hack-switch';
container.appendChild(switchLabel);
const input = document.createElement('input'); input.type = 'checkbox';
input.addEventListener('change', function() { onChangeCallback(this.checked); });
switchLabel.appendChild(input);
const slider = document.createElement('span'); slider.className = 'ql-hack-slider';
switchLabel.appendChild(slider);
return { container, input };
};
contentContainer.appendChild(createSwitch('Auto Answer', checked => { autoAnswer = checked; console.log(`[QuizletHack] Auto Answer ${autoAnswer ? 'Enabled' : 'Disabled'}`); }).container);
contentContainer.appendChild(createSwitch('Show Answers', checked => { showAnswers = checked; console.log(`[QuizletHack] Show Answers ${showAnswers ? 'Enabled' : 'Disabled'}`); if (!showAnswers) clearDynamicAnswerStyles(); }).container);
const versionLabel = document.createElement('p');
versionLabel.textContent = `UI: v${UIVersion} / Logic: v${LogicVersion}`; // Zeigt beide Versionen
versionLabel.style.fontSize = '10px'; versionLabel.style.textAlign = 'center';
versionLabel.style.marginTop = '10px'; versionLabel.style.marginBottom = '5px';
versionLabel.style.opacity = '0.7';
contentContainer.appendChild(versionLabel);
const githubContainer = document.createElement('div');
githubContainer.style.textAlign = 'center'; githubContainer.style.fontSize = '10px';
githubContainer.style.opacity = '0.9'; githubContainer.style.marginBottom = '5px';
const githubLink = (user, role) => `<a href="https://github.com/${user}" target="_blank" style="color: white; text-decoration: underline;">${user} (${role})</a>`;
githubContainer.innerHTML = `Credits: ${githubLink('Dark Shadow', 'UI')} & ${githubLink('Dark Shadow', 'Logic')}`; // Angepasste Credits
contentContainer.appendChild(githubContainer);
if (!document.getElementById('quizlet-hack-styles')) {
const style = document.createElement('style');
style.id = 'quizlet-hack-styles';
style.textContent = `
.ql-hack-main-ui a:hover { text-decoration: none; }
.ql-hack-content { max-height: 300px; opacity: 1; overflow: hidden; padding: 10px 15px; transition: max-height 0.35s ease-in-out, opacity 0.3s ease-in-out, padding 0.3s ease-in-out; box-sizing: border-box; background-color: transparent; }
.ql-hack-main-ui.minimized .ql-hack-content { max-height: 0; opacity: 0; padding-top: 0; padding-bottom: 0; }
.ql-hack-main-ui.minimized .ql-hack-handle { border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; }
.ql-hack-switch { position: relative; display: inline-block; width: 40px; height: 22px; vertical-align: middle; }
.ql-hack-switch input { opacity: 0; width: 0; height: 0; }
.ql-hack-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #555555; transition: .3s; border-radius: 22px; }
.ql-hack-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
input:checked + .ql-hack-slider { background-color: #00C853; } /* Changed to a green color */
input:focus + .ql-hack-slider { box-shadow: 0 0 2px #BBBBBB; }
input:checked + .ql-hack-slider:before { transform: translateX(18px); background-color: #FFFFFF; } /* Knob white when ON */
`;
document.head.appendChild(style);
}
let isMinimized = false;
const toggleMinimize = () => {
isMinimized = !isMinimized;
if (isMinimized) {
uiElement.classList.add('minimized');
minimizeButton.textContent = '□';
} else {
uiElement.classList.remove('minimized');
minimizeButton.textContent = '─';
}
console.log(`[QuizletHack] UI ${isMinimized ? 'Minimized' : 'Restored'}`);
};
let isDragging = false; let offsetX, offsetY;
handle.addEventListener('mousedown', (e) => {
if (e.target === closeButton || e.target === minimizeButton) {
isDragging = false; return;
}
isDragging = true;
offsetX = e.clientX - uiElement.getBoundingClientRect().left;
offsetY = e.clientY - uiElement.getBoundingClientRect().top;
handle.style.cursor = 'grabbing';
uiElement.style.userSelect = 'none';
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
let newX = e.clientX - offsetX;
let newY = e.clientY - offsetY;
const maxX = window.innerWidth - uiElement.offsetWidth;
const maxY = window.innerHeight - uiElement.offsetHeight;
newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
uiElement.style.left = newX + 'px';
uiElement.style.top = newY + 'px';
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
handle.style.cursor = 'grab';
uiElement.style.removeProperty('user-select');
}
});
if (document.body) {
document.body.appendChild(uiElement);
console.log("[QuizletHack] UI Created with Background Image.");
} else {
console.error("[QuizletHack] document.body not found when trying to append UI.");
}
} // End of createUI
// --- Core Logic Functions (AUS SCRIPT 1, angepasst) ---
// Selektoren für Quizlet-Elemente
const SELECTOR_QUESTION_TEXT = ".FormattedText"; // Für den Fragetext
const SELECTOR_ANSWER_OPTIONS = ".a1w6enf9"; // Für die Antwortmöglichkeiten
// HINWEIS: Diese Klassennamen können sich ändern, wenn Quizlet seine Seite aktualisiert.
function getPair(str) {
// Direkt aus Script 1 übernommen
let result = undefined;
if (!str || !Array.isArray(pairs)) return undefined;
pairs.forEach(function(pair) {
if (Array.isArray(pair) && pair.length >= 2) {
if (pair[0] === str) {
result = pair[1];
}
if (pair[1] === str) {
result = pair[0];
}
}
});
return result;
}
function findCorrectAnswerDetails() {
// Kombiniert Logik aus Script 1's getAnswerIndex und Script 2's findCorrectAnswerIndex
let resultIdx = undefined;
let correctAnswerText = undefined;
const questionElement = document.querySelector(SELECTOR_QUESTION_TEXT);
if (!questionElement || !questionElement.textContent) {
// console.warn("[QuizletHack] Question element not found.");
return { index: undefined, text: undefined };
}
const questionText = questionElement.textContent;
correctAnswerText = getPair(questionText);
if (correctAnswerText === undefined && pairs.length > 0) {
// console.warn(`[QuizletHack] Could not find pair for question: "${questionText}". Pairs might be stale or question format changed.`);
// Script 1 hatte hier: location.reload(); pairs = []; was aggressiv ist.
// Wir geben einfach undefined zurück, der mainLoop wird es handhaben.
return { index: undefined, text: undefined };
}
const answerElements = document.querySelectorAll(SELECTOR_ANSWER_OPTIONS);
if (answerElements && answerElements.length > 0) {
answerElements.forEach(function(elem, idx) {
if (elem && elem.textContent && elem.textContent === correctAnswerText) {
resultIdx = idx;
}
});
} else {
// console.warn("[QuizletHack] Answer option elements not found.");
}
return { index: resultIdx, text: correctAnswerText };
}
function clickAnswer(index) {
// Aus Script 1 (answerQuestion), umbenannt für Klarheit
if (typeof index !== 'number' || index < 0) return false;
try {
const answerElements = document.querySelectorAll(SELECTOR_ANSWER_OPTIONS);
if (answerElements && answerElements.length > index && answerElements[index]) {
answerElements[index].click();
return true;
} else {
// console.warn("[QuizletHack] Could not find answer element at index", index, "to click.");
return false;
}
} catch (e) {
console.error("[QuizletHack] Error clicking answer at index", index, ":", e);
return false;
}
}
function highlightCorrectAnswer(correctIndex) {
// Aus Script 1 (highlight), umbenannt für Klarheit
if (typeof correctIndex !== 'number' || correctIndex < 0) return;
try {
const answerElements = document.querySelectorAll(SELECTOR_ANSWER_OPTIONS);
if (answerElements && answerElements.length > 0) {
answerElements.forEach(function(elem, idx) {
if (elem && elem.style) {
if (idx === correctIndex) {
elem.style.color = 'rgb(152, 241, 209)'; // Hellgrün für richtig
elem.style.fontWeight = 'bold';
elem.style.opacity = '1';
} else {
elem.style.color = 'rgb(218, 69, 67)'; // Rot für falsch
elem.style.fontWeight = 'normal';
elem.style.opacity = '0.7'; // Etwas gedämpft
}
}
});
}
} catch (e) {
console.error("[QuizletHack] Error applying highlight styles:", e);
}
}
function clearDynamicAnswerStyles() {
// Beibehaltung der Funktion aus Skript 2, da sie gut ist
try {
document.querySelectorAll(SELECTOR_ANSWER_OPTIONS).forEach(function(elem) {
if (elem && elem.style) {
elem.style.color = '';
elem.style.fontWeight = '';
elem.style.opacity = '';
}
});
} catch (e) {
// console.warn("[QuizletHack] Minor error clearing answer styles:", e);
}
}
// --- XHR Interception (AUS SCRIPT 1) ---
if (!XMLHttpRequest.prototype._quizletHackIntercepted_v1_logic) { // Eindeutiger Flag-Name
const originalXhrOpen = XMLHttpRequest.prototype.open;
const originalXhrSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
this._interceptedUrl = url;
try {
return originalXhrOpen.call(this, method, url, ...rest);
} catch (e) {
console.error("[QuizletHack] Error in XHR open interceptor:", e);
throw e;
}
};
XMLHttpRequest.prototype.send = function (...args) {
try {
this.addEventListener('load', function () {
if (this.responseText && typeof this.responseText === 'string') {
let text = this.responseText;
let index = text.indexOf("42["); // Charakteristischer Prefix für Quizlet Live Daten via Socket.IO
if (index !== -1) {
try {
// Die JSON-Struktur beginnt nach "42["
// Es ist ein Array, wobei das zweite Element ([1]) die relevanten Daten enthält
let jsonDataString = text.substring(index + 2); // Entfernt "42"
// Manchmal sind mehrere Nachrichten konkateniert, wir versuchen, nur die erste zu parsen
// oder eine, die "cards" enthält
let parsedOuterArray;
try {
// Versuche, das äußere Array zu parsen
// Oft ist die Struktur 42["event", payload]
// Wir müssen das erste vollständige JSON-Array extrahieren
let bracketCount = 0;
let jsonEndIndex = 0;
for(let k=0; k < jsonDataString.length; k++) {
if(jsonDataString[k] === '[') bracketCount++;
if(jsonDataString[k] === ']') bracketCount--;
if(bracketCount === 0 && jsonDataString[k] === ']') {
jsonEndIndex = k;
break;
}
}
if (jsonEndIndex > 0) {
parsedOuterArray = JSON.parse(jsonDataString.substring(0, jsonEndIndex + 1));
} else {
// Fallback, wenn die obige Logik fehlschlägt (z.B. bei komplexeren oder verketteten Nachrichten)
// Dieser Teil ist weniger robust
parsedOuterArray = JSON.parse(jsonDataString.match(/^(\[.*?\])/)[0]);
}
} catch (e) {
// console.warn("[QuizletHack] Could not parse initial JSON array structure from XHR.", e, "Data snippet:", jsonDataString.substring(0,200));
return;
}
if (Array.isArray(parsedOuterArray) && parsedOuterArray.length > 1 &&
parsedOuterArray[1] && typeof parsedOuterArray[1] === 'object' &&
Array.isArray(parsedOuterArray[1].cards)) {
let cards = parsedOuterArray[1].cards;
let extractedPairs = cards.map(function (card){
// Sicherheitsüberprüfungen für die verschachtelten Eigenschaften
const termSide = card.cardSides && card.cardSides[0] && card.cardSides[0].media && card.cardSides[0].media[0];
const definitionSide = card.cardSides && card.cardSides[1] && card.cardSides[1].media && card.cardSides[1].media[0];
if (termSide && termSide.plainText && definitionSide && definitionSide.plainText) {
return [termSide.plainText, definitionSide.plainText];
}
// console.warn("[QuizletHack] Card with incomplete data found:", card);
return null; // Ungültige Karte
}).filter(pair => pair !== null); // Entferne ungültige Karten
if (extractedPairs.length > 0) {
pairs = extractedPairs;
console.log("[QuizletHack] Extracted " + pairs.length + " pairs:", pairs);
lastAnswer = ""; // Wichtig: lastAnswer zurücksetzen, wenn neue Paare geladen werden
} else {
// console.warn("[QuizletHack] Parsed data, but found no valid pairs in cards array.");
}
} else {
// console.log("[QuizletHack] Intercepted XHR, but not the game data structure we expected. Data:", parsedOuterArray);
}
} catch (e) {
// console.warn("[QuizletHack] Error parsing intercepted JSON data. This might be okay for non-game data.", e, "Data snippet:", text.substring(index, index + 200));
}
}
}
});
} catch (e) {
console.error("[QuizletHack] Error adding 'load' listener to XHR:", e);
}
try {
return originalXhrSend.call(this, ...args);
} catch (e) {
console.error("[QuizletHack] Error in XHR send interceptor:", e);
throw e;
}
};
XMLHttpRequest.prototype._quizletHackIntercepted_v1_logic = true; // Setze den Flag
console.log("[QuizletHack] XHR interception (v1 logic) active.");
}
// --- Main Loop (Struktur von Script 2, Logik-Aufrufe angepasst) ---
function mainGameLoop() {
try {
const SELECTOR_END_VIEW = ".StudentEndView"; // Klasse, die das Spielende anzeigt
if (document.querySelector(SELECTOR_END_VIEW)) {
if (lastAnswer !== "") { // Nur zurücksetzen, wenn es einen Wert hatte
lastAnswer = "";
console.log("[QuizletHack] Game ended, lastAnswer reset.");
}
if (showAnswers) clearDynamicAnswerStyles();
return; // Verarbeitung stoppen, wenn das Spiel vorbei ist
}
if (pairs.length === 0) {
// console.log("[QuizletHack] Waiting for pairs data..."); // Optional für Debugging
return;
}
const questionElement = document.querySelector(SELECTOR_QUESTION_TEXT);
if (!questionElement) { // Wenn kein Frageelement gefunden wird
if (showAnswers) clearDynamicAnswerStyles(); // Stile entfernen, wenn keine Frage da ist
return;
}
const { index: correctAnswerIndex, text: currentCorrectAnswerText } = findCorrectAnswerDetails();
if (correctAnswerIndex !== undefined && currentCorrectAnswerText !== undefined) {
// Auto Answer Logik
if (autoAnswer && lastAnswer !== currentCorrectAnswerText) { // Verhindert erneutes Klicken auf dieselbe Antwort
if (clickAnswer(correctAnswerIndex)) {
lastAnswer = currentCorrectAnswerText; // Merke dir die zuletzt beantwortete Frage
// console.log("[QuizletHack] Auto-answered:", currentCorrectAnswerText);
}
}
// Show Answers Logik
if (showAnswers) {
highlightCorrectAnswer(correctAnswerIndex);
} else {
clearDynamicAnswerStyles(); // Sicherstellen, dass Stile entfernt werden, wenn "Show Answers" deaktiviert ist
}
} else {
// Wenn keine korrekte Antwort gefunden wurde (z.B. zwischen Fragen oder Fehler bei der Paarsuche)
if (showAnswers) {
clearDynamicAnswerStyles();
}
// Wenn correctAnswerText undefined ist, aber eine Frage da ist, und pairs existieren,
// könnte das ein Hinweis auf ein Problem sein (z.B. eine neue Frage, die nicht in den `pairs` ist).
// Die Logik in `findCorrectAnswerDetails` versucht, dies bereits abzufangen.
}
} catch (e) {
console.error("[QuizletHack] Error in main game loop:", e);
// Optional: Intervall stoppen bei wiederholten Fehlern
// clearInterval(mainIntervalId);
}
}
// --- Initialization ---
createUI();
var mainIntervalId = setInterval(mainGameLoop, POLLING_INTERVAL_MS);
console.log(`[QuizletHack] Initialized. UI v${UIVersion}, Logic v${LogicVersion}. Main loop running every ${POLLING_INTERVAL_MS}ms.`);
})(); // End of IIFE