// ==UserScript==
// @name Mydealz Kategorieanalyse
// @description Analysiert die Kategorien der Deals eines Users für das Verrückter Wissenschaftler Badge
// @namespace Tampermonkey Scripts
// @match https://www.mydealz.de/profile/*/deals*
// @grant GM_xmlhttpRequest
// @author MD928835
// @license MIT
// @version 1.0
// @downloadURL https://update.greasyfork.icu/scripts/538850/Mydealz%20Kategorieanalyse.user.js
// @updateURL https://update.greasyfork.icu/scripts/538850/Mydealz%20Kategorieanalyse.meta.js
// ==/UserScript==
(async function() {
'use strict';
let kategorien = [];
let gefundeneKategorien = new Set();
let kategorieZaehler = new Map();
let analyseLaeuft = false;
let gesamtDeals = 0;
let logData = []; // Log-Daten sammeln
let username = ''; // Username für Dateiname
// GraphQL Query für Thread-Daten
const THREAD_QUERY = `query getThread($filter: IDFilter!) {
thread(threadId: $filter) {
title
mainGroup {
threadGroupId
threadGroupName
}
}
}`;
// Popup Template mit Overlay (10% breiter: 660px statt 600px)
const popupTemplate = `
`;
// Initiales HTML fetchen
function fetchInitialHtml(url = window.location.href) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, 'text/html');
resolve(doc);
} catch (e) {
reject(e);
}
} else {
reject(new Error(`HTTP Status ${response.status}`));
}
},
onerror: function(error) {
reject(error);
}
});
});
}
// Seitenzahl aus Button-Text ermitteln
function pageNum(sel) {
const el = document.querySelector(sel);
if (!el) return null;
const m = el.textContent.match(/\d+/);
return m ? parseInt(m[0], 10) : null;
}
// Username aus URL extrahieren
function getUsernameFromUrl() {
const match = window.location.pathname.match(/\/profile\/([^\/]+)/);
return match ? match[1] : 'unknown_user';
}
// GraphQL-Anfrage für Thread-Daten
async function fetchThreadData(threadId) {
try {
const response = await fetch('https://www.mydealz.de/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: THREAD_QUERY,
variables: { filter: { eq: threadId } }
})
});
const result = await response.json();
return result.data?.thread || null;
} catch (error) {
console.error(`Fehler beim Abrufen der Thread-Daten für ${threadId}:`, error);
return null;
}
}
// Log-Eintrag hinzufügen (nur sammeln, nicht herunterladen)
function addLogEntry(entry) {
logData.push(entry);
console.log(entry); // Für Live-Debug in Console
}
// Finale Datei herunterladen
function downloadDebugFile() {
const logContent = logData.join('\n');
const blob = new Blob([logContent], { type: 'text/plain;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `deals_von_${username}.txt`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
console.log(`Dealliste heruntergeladen: deals_von_${username}.txt`);
}
async function fetchKategorien() {
const query = `
query siteNavigationMenu {
groups: groups(default: {is: true}) {
threadGroupId
threadGroupName
threadGroupUrlName
}
}`;
try {
const response = await fetch('https://www.mydealz.de/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});
const data = await response.json();
return data.data.groups.map(group => ({
id: group.threadGroupId,
name: group.threadGroupName,
url: group.threadGroupUrlName
}));
} catch (error) {
console.error('Fehler beim Laden der Kategorien:', error);
return [];
}
}
// Button auf der Dealübersichtsseite hinzufügen
function addAnalyzeButton() {
// Prüfe ob wir auf einer User-Deals-Seite sind
if (!window.location.pathname.match(/\/profile\/[^\/]+\/deals/)) {
return;
}
// Prüfe ob Button bereits existiert
if (document.querySelector('#kategorieanalyse-btn')) {
return;
}
// Finde einen geeigneten Container (z.B. oberhalb der Deal-Liste)
const container = document.querySelector('.userProfile-deals') ||
document.querySelector('.thread-list') ||
document.querySelector('main');
if (!container) return;
// Erstelle Button
const analyzeButton = document.createElement('button');
analyzeButton.id = 'kategorieanalyse-btn';
analyzeButton.className = 'button button--type-primary button--mode-brand';
analyzeButton.style.cssText = `
margin: 20px 0;
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
`;
analyzeButton.innerHTML = `
Kategorieanalyse starten
`;
// Event Listener
analyzeButton.addEventListener('click', (e) => {
e.preventDefault();
showAnalyzeDialog();
});
// Button einfügen (am Anfang des Containers)
container.insertBefore(analyzeButton, container.firstChild);
}
function renderCategories(categories) {
// KORRIGIERTE Filterung: Pseudo-Kategorien (ID ≥ 800000) ausschließen
const normalCats = categories
.filter(cat => cat.name !== 'Freebies' &&
categories.indexOf(cat) < categories.findIndex(c => c.name === 'Freebies') &&
parseInt(cat.id) < 800000) // Pseudo-Kategorien ausschließen
.sort((a, b) => a.name.localeCompare(b.name));
const half = Math.ceil(normalCats.length / 2);
const leftColumn = normalCats.slice(0, half);
const rightColumn = normalCats.slice(half);
return `
${leftColumn.map(cat => renderItem(cat)).join('')}
${rightColumn.map(cat => renderItem(cat)).join('')}
`;
}
function renderItem(cat) {
const isFound = gefundeneKategorien.has(parseInt(cat.id));
const dealCount = kategorieZaehler.get(parseInt(cat.id)) || 0;
const percentage = gesamtDeals > 0 ? Math.round((dealCount / gesamtDeals) * 100) : 0;
const textStyle = isFound ? 'text-decoration: line-through; color: #999;' : '';
const countText = isFound ? ` (${dealCount} | ${percentage}%)` : '';
return `
`;
}
function updateProgress() {
const checkedCheckboxes = document.querySelectorAll('input[name="category"]:checked').length;
const availableCheckboxes = document.querySelectorAll('input[name="category"]').length;
const percentage = availableCheckboxes > 0 ? Math.round((checkedCheckboxes / availableCheckboxes) * 100) : 0;
// Debug-Ausgabe für Kontrolle
console.log(`Progress Debug: ${checkedCheckboxes}/${availableCheckboxes} Checkboxen angehakt = ${percentage}%`);
console.log('Gefundene Kategorien (Set):', gefundeneKategorien.size);
console.log('Angehakte Checkboxen (DOM):', checkedCheckboxes);
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
if (progressBar && progressText) {
progressBar.style.width = percentage + '%';
progressText.textContent = `${percentage}% (${checkedCheckboxes}/${availableCheckboxes})`;
// NUR bei echter 100%-Abdeckung UND abgeschlossener Analyse
// WICHTIG: Prüfe die REALEN DOM-Werte, nicht die theoretischen!
if (checkedCheckboxes >= availableCheckboxes && checkedCheckboxes > 0 && !analyseLaeuft) {
progressText.textContent = '100% - Du bist komplett verrückt!';
progressBar.style.backgroundColor = '#28a745';
}
}
}
function updateAnalyzeButton(currentPage, maxPage) {
const progressInfo = document.getElementById('progressInfo');
if (analyseLaeuft) {
if (progressInfo) {
progressInfo.textContent = `Analysiere Dealseite ${currentPage} / ${maxPage}`;
}
} else {
if (progressInfo) {
progressInfo.textContent = '';
}
}
}
// Nur Checkmark sofort setzen, keine komplette Neuzeichnung
function setCheckmarkForCategory(categoryId) {
const checkbox = document.querySelector(`input[name="category"][value="${categoryId}"]`);
if (checkbox && !checkbox.checked) {
checkbox.checked = true;
checkbox.disabled = true;
// Auch das Label visuell aktualisieren
const label = checkbox.closest('label');
if (label) {
label.style.textDecoration = 'line-through';
label.style.color = '#999';
}
console.log(`Checkmark gesetzt für Kategorie ${categoryId}`);
}
}
// Analysiert eine Seite per Fetch und GraphQL
async function analyzePageByFetch(pageNum) {
const currentUrl = new URL(window.location.href);
const baseUrl = `${currentUrl.origin}${currentUrl.pathname}`;
const fetchUrl = `${baseUrl}?page=${pageNum}`;
try {
console.log(`Lade Seite ${pageNum} per Fetch: ${fetchUrl}`);
const doc = await fetchInitialHtml(fetchUrl);
const threads = doc.querySelectorAll('article.thread');
console.log(`Seite ${pageNum}: ${threads.length} Threads im initialen HTML gefunden`);
for (let index = 0; index < threads.length; index++) {
// Prüfe ob Analyse abgebrochen wurde
if (!analyseLaeuft) {
console.log('Analyse wurde abgebrochen');
return;
}
const article = threads[index];
const threadId = article.id; // "thread_2584326"
if (!threadId) {
console.warn(`Thread #${index}: Keine ID gefunden`);
continue;
}
// WICHTIG: Nur die numerische ID extrahieren
const numericThreadId = threadId.replace('thread_', ''); // "2584326"
try {
console.log(`Thread #${index}: Lade Daten für ${numericThreadId}`);
// GraphQL-Anfrage mit numerischer ID
const threadData = await fetchThreadData(numericThreadId);
if (!threadData) {
console.warn(`Thread #${index}: Keine Daten für ${numericThreadId} erhalten`);
continue;
}
if (!threadData.mainGroup?.threadGroupId) {
console.warn(`Thread #${index}: Keine Kategorie für ${numericThreadId} gefunden`);
continue;
}
const groupId = parseInt(threadData.mainGroup.threadGroupId);
const groupName = threadData.mainGroup.threadGroupName;
const title = threadData.title || 'Unbekannter Titel';
const dealUrl = `https://www.mydealz.de/${numericThreadId}`;
// Log-Eintrag: CSV-Zeile mit Titel
const logEntry = `${groupId},"${groupName}","${dealUrl}","${title}"`;
addLogEntry(logEntry);
// Zähle Deals pro Kategorie
kategorieZaehler.set(groupId, (kategorieZaehler.get(groupId) || 0) + 1);
gesamtDeals++;
// Neue Kategorie gefunden
if (!gefundeneKategorien.has(groupId)) {
gefundeneKategorien.add(groupId);
console.log(`NEUE Kategorie gefunden: ${groupName} (ID: ${groupId})`);
// SOFORT Checkmark setzen
setCheckmarkForCategory(groupId);
updateProgress();
}
console.log(`Thread #${index} verarbeitet: ${title} -> ${groupName}`);
// Kurze Pause zwischen GraphQL-Anfragen
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`Fehler bei Thread #${index} (${numericThreadId}):`, error);
}
}
} catch (error) {
console.error(`Fehler beim Laden von Seite ${pageNum}:`, error);
}
}
async function analyzeUserDeals() {
if (analyseLaeuft) {
// Analyse abbrechen durch Reload
window.location.reload();
return;
}
analyseLaeuft = true;
// Username aus URL extrahieren
username = getUsernameFromUrl();
// Reset der Zähler
gefundeneKategorien.clear();
kategorieZaehler.clear();
gesamtDeals = 0;
logData = ['KategorieID,Kategoriename,Deal-URL,Deal-Titel']; // CSV-Header mit Titel
const downloadBtn = document.getElementById('downloadBtn');
const progressInfo = document.getElementById('progressInfo');
if (downloadBtn) {
downloadBtn.style.display = 'none';
}
if (progressInfo) {
progressInfo.textContent = 'Starte Analyse...';
}
try {
// Maximale Seitenzahl aus der LIVE-Seite ermitteln
const maxPage = pageNum('button[aria-label="Letzte Seite"]') || 1;
console.log(`Starte GraphQL-basierte Analyse von ${maxPage} Seiten für User: ${username}`);
// ALLE Seiten analysieren - KEIN vorzeitiger Abbruch bei 100%
for (let currentPage = 1; currentPage <= maxPage; currentPage++) {
// Prüfe ob Analyse abgebrochen wurde
if (!analyseLaeuft) {
console.log('Analyse wurde abgebrochen');
return;
}
console.log(`Analysiere Seite ${currentPage}/${maxPage}`);
updateAnalyzeButton(currentPage, maxPage);
await analyzePageByFetch(currentPage);
// Kurze Pause zwischen Seiten
await new Promise(resolve => setTimeout(resolve, 500));
}
} catch (error) {
console.error('Fehler bei der Analyse:', error);
} finally {
analyseLaeuft = false;
// NUR Download-Button anzeigen
if (downloadBtn) {
downloadBtn.style.display = 'inline-block';
downloadBtn.onclick = downloadDebugFile;
}
if (progressInfo) {
progressInfo.textContent = 'Analyse abgeschlossen';
}
// Debug: Finale Ausgabe
console.log('=== ANALYSE ABGESCHLOSSEN ===');
console.log(`Gefundene Kategorien: ${gefundeneKategorien.size}`);
console.log(`Gesamt Deals: ${gesamtDeals}`);
console.log('Kategorie-Zähler:', Object.fromEntries(kategorieZaehler));
// WICHTIG: NUR die Texte aktualisieren, NICHT das HTML überschreiben
setTimeout(() => {
gefundeneKategorien.forEach(categoryId => {
const checkbox = document.querySelector(`input[name="category"][value="${categoryId}"]`);
if (checkbox) {
// Sicherstellen, dass Checkbox gesetzt ist
checkbox.checked = true;
checkbox.disabled = true;
const label = checkbox.closest('label');
const span = label.querySelector('span');
const dealCount = kategorieZaehler.get(categoryId) || 0;
const percentage = gesamtDeals > 0 ? Math.round((dealCount / gesamtDeals) * 100) : 0;
// Nur den Text aktualisieren
const categoryName = checkbox.dataset.name;
span.textContent = `${categoryName} (${dealCount} | ${percentage}%)`;
// Formatierung explizit setzen
label.style.textDecoration = 'line-through';
label.style.color = '#999';
console.log(`Finale Aktualisierung für Kategorie ${categoryName}: ${dealCount} Deals (${percentage}%)`);
}
});
updateProgress(); // Finale Aktualisierung mit korrekter Zählung
}, 100);
}
}
function updateCategoryDisplay() {
const form = document.getElementById('categoryForm');
if (form && kategorien.length > 0) {
form.innerHTML = renderCategories(kategorien);
console.log(`Popup initial aktualisiert: ${kategorien.length} Kategorien geladen`);
}
}
function closePopup() {
if (analyseLaeuft) {
// Analyse abbrechen durch Reload
window.location.reload();
return;
}
const popup = document.querySelector('.popover');
if (popup) popup.remove();
const overlay = document.querySelector('.modal-backdrop');
if (overlay) overlay.remove();
addAnalyzeButton();
}
function showAnalyzeDialog(event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
const existingPopup = document.querySelector('.popover');
if (existingPopup) {
existingPopup.remove();
const existingOverlay = document.querySelector('.modal-backdrop');
if (existingOverlay) existingOverlay.remove();
}
document.body.insertAdjacentHTML('beforeend', popupTemplate);
initializePopup();
}
function initializePopup() {
const form = document.getElementById('categoryForm');
fetchKategorien().then(categories => {
kategorien = categories;
form.innerHTML = renderCategories(categories);
updateProgress();
});
const closeBtnOld = document.getElementById('closeBtn');
const closeBtn = closeBtnOld.cloneNode(true);
closeBtnOld.parentNode.replaceChild(closeBtn, closeBtnOld);
const downloadBtnOld = document.getElementById('downloadBtn');
const downloadBtn = downloadBtnOld.cloneNode(true);
downloadBtnOld.parentNode.replaceChild(downloadBtn, downloadBtnOld);
closeBtn.addEventListener('click', () => closePopup());
// Starte Analyse automatisch
analyzeUserDeals();
}
function init() {
// Button auf Deal-Übersichtsseite hinzufügen
addAnalyzeButton();
// Observer für dynamische Inhalte
const observer = new MutationObserver(() => {
addAnalyzeButton();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
init();
})();