Zeigt die letzten Kommentare eines Benutzers an
Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyfork.org/scripts/528796/1803908/MyDealz%20Comment%20Viewer.js
// ==UserScript==
// @name MyDealz Comment Viewer
// @namespace http://tampermonkey.net/
// @version 2.6
// @description Zeigt die letzten Kommentare eines Benutzers an
// @author MD928835
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Modal-Styles einmalig einfügen
const style = document.createElement('style');
style.textContent = `
#mdcv-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.6);
z-index: 999999; display: flex; align-items: center; justify-content: center;
}
#mdcv-modal {
background: #f5f5f5; width: 90vw; max-width: 1000px;
height: 85vh; border-radius: 8px; display: flex; flex-direction: column;
overflow: hidden; box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
#mdcv-header {
background: #00a000; height: 56px; display: flex;
align-items: center; justify-content: center;
color: white; font-size: 20px; position: relative; flex-shrink: 0;
}
#mdcv-header img { height: 36px; position: absolute; left: 16px; }
#mdcv-close {
position: absolute; right: 16px; background: none; border: none;
color: white; font-size: 24px; cursor: pointer; line-height: 1;
}
#mdcv-sort { text-align: center; padding: 10px; background: #fff;
border-bottom: 1px solid #ddd; flex-shrink: 0; }
#mdcv-body { overflow-y: auto; padding: 16px; flex: 1; }
.mdcv-card {
background: white; padding: 1rem; margin: 0.75rem 0;
border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#mdcv-mute {
display: flex; align-items: center; gap: 6px;
padding: 6px 12px; border-radius: 4px; cursor: pointer;
background: none; border: 1px solid white; color: white;
position: absolute; right: 56px; font-size: 13px;
}
#mdcv-mute:disabled { opacity: 0.5; cursor: not-allowed; }
`;
document.head.appendChild(style);
window.viewUserComments = async function(username) {
const fetchDealTitle = async (threadId) => {
const query = `query getThread($filter: IDFilter!) {
thread(threadId: $filter) { title }
}`;
try {
const res = await fetch("https://www.mydealz.de/graphql", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables: { filter: { eq: threadId } } })
});
const result = await res.json();
return result.data.thread.title || "Titel nicht verfügbar";
} catch {
return "Titel nicht verfügbar";
}
};
// Ladeindikator zeigen
showModal(username, '<p style="text-align:center;padding:2rem">Lade Kommentare…</p>', [], null, null);
try {
const response = await fetch(`https://www.mydealz.de/profile/${username}?page=1`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
const pattern = /href=https:\/\/www\.mydealz\.de\/.*?-(\d+)#(?:comment|reply)-(\d+)/g;
const ids = [...html.matchAll(pattern)].map(m => ({
threadId: m[1],
commentId: m[2],
url: m[0].replace('href=', '')
}));
// User-Metadaten (mutable, isMuted)
const userQuery = `query userProfile($username: String) {
user(username: $username) { mutable isMuted }
}`;
const userRes = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: userQuery, variables: { username } })
});
const userData = await userRes.json();
const { mutable, isMuted: initialMuted } = userData.data.user;
// Kommentare parallel laden
const commentQuery = `query comment($id: ID!) {
comment(id: $id) { preparedHtmlContent createdAt createdAtTs }
}`;
const fetchPromises = ids.map(async ({ threadId, commentId, url }) => {
try {
const [commentRes, title] = await Promise.all([
fetch("https://www.mydealz.de/graphql", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: commentQuery, variables: { id: commentId } })
}).then(r => r.json()),
fetchDealTitle(threadId)
]);
const cd = commentRes?.data?.comment;
if (!cd) return null;
const comment = cd.preparedHtmlContent.replace(/<img[^>]*>/g, '');
const date = new Date(cd.createdAtTs * 1000)
.toLocaleString('de-DE', {
day: '2-digit', month: '2-digit', year: '2-digit',
hour: '2-digit', minute: '2-digit'
}).replace(',', '');
return { title, comment, date, createdAt: cd.createdAt,
dealId: threadId, commentId, url };
} catch {
return null;
}
});
const results = (await Promise.all(fetchPromises)).filter(Boolean);
showModal(username, null, results, mutable, initialMuted);
} catch (error) {
console.error("Fehler:", error);
const body = document.getElementById('mdcv-body');
if (body) body.innerHTML = `<p style="color:red">Fehler: ${error.message}</p>`;
}
};
function renderCards(results) {
return results.map(r => `
<div class="mdcv-card">
<span title="${r.date}">${r.createdAt}</span>
<b>${r.title}</b><br>
${r.comment}<br>
<svg width="15" height="16" style="vertical-align:middle">
<use xlink:href="/assets/img/ico_632f5.svg#comment"></use>
</svg>
<a href="${r.url}" target="_blank">Zum Kommentar</a>
</div>`).join('');
}
function showModal(username, loadingHtml, results, mutable, initialMuted) {
// Altes Modal entfernen
document.getElementById('mdcv-overlay')?.remove();
let isMuted = initialMuted;
const overlay = document.createElement('div');
overlay.id = 'mdcv-overlay';
const modal = document.createElement('div');
modal.id = 'mdcv-modal';
// Header
const header = document.createElement('div');
header.id = 'mdcv-header';
header.innerHTML = `
<img src="https://www.mydealz.de/assets/img/logo/default-light_d4b86.svg" alt="mydealz">
<a href="https://www.mydealz.de/profile/${username}"
style="color:white;text-decoration:none" target="_blank">
${username}s letzte ${results.length} Kommentare
</a>`;
// FIX #3: Mute-Button existiert jetzt tatsächlich im DOM
if (mutable) {
const muteBtn = document.createElement('button');
muteBtn.id = 'mdcv-mute';
muteBtn.innerHTML = `
<svg width="15" height="15">
<use id="mdcv-mute-icon" xlink:href="/assets/img/ico_632f5.svg#${isMuted ? 'unmute' : 'mute'}"></use>
</svg>
<span id="mdcv-mute-text">${username} ${isMuted ? 'nicht mehr stumm schalten' : 'stumm schalten'}</span>`;
muteBtn.addEventListener('click', async () => {
muteBtn.disabled = true;
const endpoint = isMuted
? `/profile/${username}/unmute`
: `/profile/${username}/mute`;
try {
const xsrf = document.cookie.split('xsrf_t=')[1]?.split(';')[0]?.replace(/"/g, '');
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'X-Request-Type': 'application/vnd.pepper.v1+json',
'X-Requested-With': 'XMLHttpRequest',
'X-Pepper-Txn': 'user.profile.overview',
'X-XSRF-TOKEN': xsrf
}
});
const data = await res.json();
if (data.status === 'success') {
isMuted = !isMuted;
document.getElementById('mdcv-mute-text').textContent =
`${username} ${isMuted ? 'nicht mehr stumm schalten' : 'stumm schalten'}`;
document.getElementById('mdcv-mute-icon')
.setAttribute('xlink:href',
`/assets/img/ico_632f5.svg#${isMuted ? 'unmute' : 'mute'}`);
}
} catch (e) {
console.error('Mute-Fehler:', e);
} finally {
muteBtn.disabled = false;
}
});
header.appendChild(muteBtn);
}
const closeBtn = document.createElement('button');
closeBtn.id = 'mdcv-close';
closeBtn.textContent = '×';
closeBtn.title = 'Schließen';
closeBtn.addEventListener('click', () => overlay.remove());
header.appendChild(closeBtn);
// Sortierung
const sort = document.createElement('div');
sort.id = 'mdcv-sort';
sort.innerHTML = `
Kommentare sortieren nach
<label><input type="radio" name="mdcv-sort" checked value="all"> alle chronologisch</label>
<label><input type="radio" name="mdcv-sort" value="deal"> beitragschronologisch</label>`;
// Body
const body = document.createElement('div');
body.id = 'mdcv-body';
body.innerHTML = loadingHtml || renderCards(results);
// Sortierung — kein sessionStorage mehr nötig
sort.addEventListener('change', (e) => {
const sorted = [...results];
if (e.target.value === 'deal') {
sorted.sort((a, b) =>
b.dealId !== a.dealId
? b.dealId - a.dealId
: b.commentId - a.commentId);
} else {
sorted.sort((a, b) => b.commentId - a.commentId);
}
body.innerHTML = renderCards(sorted);
});
// Klick außerhalb schließt Modal
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.remove();
});
modal.appendChild(header);
modal.appendChild(sort);
modal.appendChild(body);
overlay.appendChild(modal);
document.body.appendChild(overlay);
}
})();