Mydealz User Profil Enhancer

Erweitert die Profilbuttons um zusätzliche Funktionen

目前为 2025-03-02 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Mydealz User Profil Enhancer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Erweitert die Profilbuttons um zusätzliche Funktionen
  6. // @author MD928835
  7. // @license MIT
  8. // @match https://www.mydealz.de/*
  9. // @require https://update.gf.qytechs.cn/scripts/528580/1545878/MyDealz%20Reactions%20Viewer%202025.js
  10. // @grant GM_xmlhttpRequest
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // CSS zur sofortigen Ausblendung hinzufügen
  17. const style = document.createElement('style');
  18. style.textContent = `
  19. /* Originalen Button sofort ausblenden */
  20. .popover a.width--all-12.space--mt-2.button:not(.custom-button) {
  21. display: none !important;
  22. }
  23.  
  24. /* Badges sofort ausblenden */
  25. .popover .flex.gap-1 {
  26. visibility: hidden !important;
  27. }
  28.  
  29. /* Neue Buttons normal anzeigen */
  30. .popover .custom-buttons .button {
  31. display: flex !important;
  32. }
  33. `;
  34. document.head.appendChild(style);
  35.  
  36. const observer = new MutationObserver((mutations) => {
  37. mutations.forEach((mutation) => {
  38. const popover = document.querySelector('.popover--visible');
  39. if (popover) {
  40. setTimeout(() => modifyPopup(popover), 100);
  41. }
  42. });
  43. });
  44.  
  45. async function modifyPopup(popover) {
  46. const profileBtn = popover.querySelector('a.width--all-12.space--mt-2.button');
  47. if (!profileBtn || popover.querySelector('.custom-buttons')) return;
  48.  
  49. const username = profileBtn.href.split('/profile/')[1];
  50. const container = profileBtn.parentElement;
  51. container.classList.add('custom-buttons');
  52.  
  53. // GraphQL Query für Online-Status und Beitrittsdatum
  54. const query = `query userProfile($username: String) {
  55. user(username: $username) {
  56. joinedAgo isOnline
  57. }
  58. }`;
  59.  
  60. try {
  61. const response = await fetch('/graphql', {
  62. method: 'POST',
  63. headers: {
  64. 'Content-Type': 'application/json'
  65. },
  66. body: JSON.stringify({
  67. query,
  68. variables: {
  69. username
  70. }
  71. })
  72. });
  73. const data = await response.json();
  74. const {
  75. isOnline,
  76. joinedAgo
  77. } = data.data.user;
  78.  
  79. // Mitgliedschaftsdauer aktualisieren
  80. const membershipElement = popover.querySelector('.overflow--wrap-off.size--all-s');
  81. if (membershipElement) {
  82. membershipElement.textContent = `Dabei seit ${joinedAgo}`;
  83. }
  84.  
  85. // Zeitangabe von der Profilseite holen
  86. let lastActivityTime = 'unbekannt';
  87. try {
  88. const profileHtml = await fetch(`https://www.mydealz.de/profile/${username}`);
  89. const tempDiv = document.createElement('div');
  90. tempDiv.innerHTML = await profileHtml.text();
  91. const timeElement = tempDiv.querySelector('.userProfile-action-item .overflow--wrap-break .mute--text');
  92. if (timeElement) {
  93. lastActivityTime = timeElement.textContent.trim();
  94. }
  95. } catch (error) {
  96. console.error('Fehler beim Abrufen der Profilseite:', error);
  97. }
  98.  
  99. // Badge-Container durch Online-Status ersetzen
  100. const badgeContainer = popover.querySelector('.flex.gap-1');
  101. if (badgeContainer) {
  102. const statusContainer = document.createElement('div');
  103. // statusContainer.className = 'size--all-s color--text-TranslucentSecondary space--mt-2 space--mb-4';
  104. statusContainer.className = 'size--all-s space--mt-2 space--mb-4';
  105. const status = isOnline ? 'ON_line' : 'OFF_line';
  106. statusContainer.textContent = `${status}, zuletzt aktiv ${lastActivityTime}`;
  107. badgeContainer.replaceWith(statusContainer);
  108. }
  109.  
  110. // Buttons Container erstellen
  111. const btnContainer = document.createElement('div');
  112. btnContainer.className = 'flex flex--grow-1 gap--all-2';
  113. btnContainer.style.gap = '5px';
  114. btnContainer.style.width = '100%';
  115.  
  116. // Profil Button
  117. const profileButton = document.createElement('a');
  118. profileButton.href = `/profile/${username}`;
  119. profileButton.className = 'flex button button--shape-circle button--type-secondary button--mode-default';
  120. profileButton.style.flex = '1';
  121. profileButton.innerHTML = `<svg width="17" height="14" class="icon icon--mail"><use xlink:href="/assets/img/ico_632f5.svg#person"></use></svg><span class="space--ml-2"> Profil </span>`;
  122.  
  123. // Nachricht Button
  124. const messageButton = document.createElement('button');
  125. messageButton.type = 'button';
  126. messageButton.className = 'flex button button--shape-circle button--type-secondary button--mode-default';
  127. messageButton.style.flex = '1';
  128. messageButton.innerHTML = `<svg width="17" height="14" class="icon icon--mail"><use xlink:href="/assets/img/ico_632f5.svg#mail"></use></svg><span class="space--ml-2"> Nachricht </span>`;
  129.  
  130. messageButton.onclick = async () => {
  131. const username = document.querySelector('.popover--visible a[href^="/profile/"]')?.href.split('/profile/')[1];
  132. if (!username) return;
  133.  
  134. try {
  135. // GET-Request zur Prüfung des Inhalts
  136. const response = await fetch(`/profile/messages/${username}`);
  137. const html = await response.text();
  138.  
  139. // Prüfen, ob der Username im HTML vorkommt
  140. const isSpecificMessagePage = html.includes(`<span class="size--all-l text--b space--mr-1">${username}</span>`);
  141.  
  142. if (isSpecificMessagePage) {
  143. // Bei existierendem User direkt zur Nachrichtenseite
  144. const win = window.open(`/profile/messages/${username}`, '_blank');
  145.  
  146. if (win) {
  147. win.addEventListener('load', () => {
  148. const observer = new MutationObserver((mutations, obs) => {
  149. const sendButton = win.document.querySelector('button[data-t="sendButton"]');
  150. if (sendButton) {
  151. sendButton.click();
  152. obs.disconnect();
  153. }
  154. });
  155.  
  156. observer.observe(win.document.body, {
  157. childList: true,
  158. subtree: true
  159. });
  160.  
  161. setTimeout(() => observer.disconnect(), 3000);
  162. });
  163. }
  164. } else {
  165. // Bei nicht-existierendem User zur Profilseite
  166. const win = window.open(`/profile/${username}`, '_blank');
  167.  
  168. if (win) {
  169. win.addEventListener('load', () => {
  170. const observer = new MutationObserver((mutations, obs) => {
  171. const sendButton = win.document.querySelector('button[data-t="sendButton"]');
  172. if (sendButton) {
  173. sendButton.click();
  174. obs.disconnect();
  175. }
  176. });
  177.  
  178. observer.observe(win.document.body, {
  179. childList: true,
  180. subtree: true
  181. });
  182.  
  183. setTimeout(() => observer.disconnect(), 3000);
  184. });
  185. }
  186. }
  187. } catch (error) {
  188. console.error('Fehler beim Prüfen der Nachrichtenseite:', error);
  189. window.open(`/profile/${username}`, '_blank');
  190. }
  191. };
  192.  
  193. // Buttons hinzufügen
  194. btnContainer.appendChild(profileButton);
  195. btnContainer.appendChild(messageButton);
  196.  
  197. // Alten Button ersetzen
  198. profileBtn.replaceWith(btnContainer);
  199.  
  200. // Statistikbereich finden und "letzte anzeigen" Link hinzufügen
  201. setTimeout(() => {
  202. const kommentareElement = Array.from(popover.querySelectorAll('li.lbox--f.lbox--v-3 .size--all-s'))
  203. .find(el => el.textContent.includes('Kommentare'));
  204.  
  205. if (kommentareElement) {
  206. // "letzte anzeigen" Link erstellen
  207. const linkElement = document.createElement('span');
  208. linkElement.className = 'showCommentsBtn';
  209. linkElement.textContent = 'anzeigen';
  210. linkElement.style.backgroundColor = '#e6f7e6';
  211. linkElement.style.padding = '0 4px';
  212. linkElement.style.borderRadius = '3px';
  213. linkElement.style.cursor = 'pointer';
  214. linkElement.style.marginLeft = '5px';
  215. linkElement.style.fontSize = '14px';
  216.  
  217. // Funktionalität des ehemaligen Vote-Buttons übernehmen
  218. linkElement.onclick = async () => {
  219. const CONFIG = {
  220. BATCH_SIZE: 5,
  221. MAX_COMMENTS_PER_PAGE: 20
  222. };
  223.  
  224. // Username aus dem PopUp holen
  225. const p = document.querySelector('.popover--visible');
  226. const username = p?.querySelector('a[href^="/profile/"]')?.getAttribute('href')?.split('/')[2];
  227. if (!username) return;
  228.  
  229. // Progress Bar
  230. const progress = document.createElement('div');
  231. progress.style.cssText = 'position:fixed;top:0;left:0;height:3px;background:#4CAF50;z-index:9999;';
  232. document.body.appendChild(progress);
  233.  
  234. try {
  235. const response = await fetch(`https://www.mydealz.de/profile/${username}?page=1`);
  236. if (!response.ok) throw new Error(`HTTP error! status:${response.status}`);
  237. const html = await response.text();
  238. const pattern = /href=https:\/\/www\.mydealz\.de\/.*?-(\d+)#(?:comment|reply)-(\d+)/g;
  239. const matches_raw = [...html.matchAll(pattern)];
  240.  
  241. const tempDiv = document.createElement('div');
  242. tempDiv.innerHTML = html;
  243. const titles = [];
  244. tempDiv.querySelectorAll('.userHtml').forEach(item => {
  245. if (item.textContent.includes('kommentiert')) {
  246. const strongTag = item.querySelector('strong');
  247. if (strongTag) titles.push(strongTag.textContent);
  248. }
  249. });
  250.  
  251. const pageResults = [];
  252. const commentPromises = matches_raw.map((match, index) =>
  253. fetch("https://www.mydealz.de/graphql", {
  254. method: 'POST',
  255. headers: {
  256. 'Content-Type': 'application/json'
  257. },
  258. body: JSON.stringify({
  259. query: 'query comment($id: ID!) { comment(id: $id) { preparedHtmlContent createdAt createdAtTs } }',
  260. variables: {
  261. id: match[2]
  262. }
  263. })
  264. })
  265. .then(res => res.json())
  266. .then(data => {
  267. progress.style.width = `${(index / matches_raw.length) * 100}%`;
  268. if (!data?.data?.comment) return null;
  269. const comment = data.data.comment.preparedHtmlContent.replace(/<img[^>]*>/g, '');
  270. const date = new Date(data.data.comment.createdAtTs * 1000)
  271. .toLocaleString('de-DE', {
  272. day: '2-digit',
  273. month: '2-digit',
  274. year: '2-digit',
  275. hour: '2-digit',
  276. minute: '2-digit'
  277. })
  278. .replace(',', '');
  279. const result = {
  280. html: `<div class="comment-card" style="background-color:white;padding:1rem;margin:0.75rem 0;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1);"><span title="${date}">${data.data.comment.createdAt}</span> <b>${titles[index]}</b><br>${comment}<br><svg width="15px" height="16px" class="icon icon--comment" style="vertical-align: middle"><use xlink:href="/assets/img/ico_632f5.svg#comment"></use></svg> <a href='${match[0].replace('href=','')}'target='_blank'>Zum Kommentar</a></div>`,
  281. title: titles[index],
  282. comment: comment.replace(/<blockquote>.*?<\/blockquote>/g, ''),
  283. dealId: match[1],
  284. commentId: match[2]
  285. };
  286. pageResults.push(result);
  287. return result;
  288. })
  289. .catch(() => null)
  290. );
  291.  
  292. await Promise.all(commentPromises);
  293.  
  294. const flatResults = pageResults.filter(r => r);
  295. flatResults.sort((a, b) => b.commentId - a.commentId);
  296.  
  297. sessionStorage.setItem('mydealz_comments', JSON.stringify(flatResults));
  298. let clipboardString = '$' + username + '$' + flatResults.map(r => `${r.title}|${r.comment}`).join('||').replace(/<[^>]*>/g, '').replace(/(\r\n|\n|\r)/gm, ' ').replace(/"/g, "'").trim();
  299.  
  300. const resultWindow = window.open("", "Results", "width=1000,height=700,location=no,menubar=no,toolbar=no,status=no,titlebar=no");
  301. if (resultWindow) {
  302. resultWindow.document.write(`<html><head><title>Schlechter Tag oder schlechter User?</title><script>function copyToClip(){navigator.clipboard.writeText(${JSON.stringify(clipboardString)}).then(()=>document.querySelector("button").textContent="Kopiert!").catch(e=>alert(e))}function sortComments(type){let comments=JSON.parse(sessionStorage.getItem('mydealz_comments'));if(type==='all'){comments.sort((a,b)=>b.commentId-a.commentId);}else{comments.sort((a,b)=>b.dealId===a.dealId?b.commentId-a.commentId:b.dealId-a.dealId);}document.getElementById('comments-container').innerHTML=comments.map(r=>r.html).join('');}</script></head><body style="margin:0;padding:0;background:#f5f5f5"><div style="background:#005293;height:56px;display:flex;align-items:center;color:white;font-size:24px;text-align:center"><img src="https://www.mydealz.de/assets/img/logo/default-light_d4b86.svg" style="height:40px;margin-left:20px"><div style="flex-grow:1;text-align:center"><a href="https://www.mydealz.de/profile/${username}" style="color:white;text-decoration:none" target="_blank">${username}s letzte ${flatResults.length} Kommentare</a></div></div><div style="text-align:center;padding:10px"><button style="padding:10px" onclick="copyToClip()">In Zwischenablage kopieren</button></div><div style="text-align:center;padding:10px">Kommentare sortieren nach <label><input type="radio" name="sort" checked onclick="sortComments('all')"> alle chronologisch</label> <label><input type="radio" name="sort" onclick="sortComments('deal')"> beitragschronologisch</label></div><div id="comments-container" style="margin:20px">${flatResults.map(r => r.html).join('')}</div></body></html>`);
  303. resultWindow.document.close();
  304. resultWindow.focus();
  305. } else {
  306. alert("Popup blockiert!");
  307. }
  308.  
  309. progress.remove();
  310. } catch (err) {
  311. alert(`Fehler: ${err.message}`);
  312. progress.remove();
  313. }
  314. };
  315.  
  316. // Link zum Kommentare-Element hinzufügen
  317. kommentareElement.appendChild(document.createTextNode(' '));
  318. kommentareElement.appendChild(linkElement);
  319. }
  320. const reactionsElement = Array.from(popover.querySelectorAll('li.lbox--f.lbox--v-3 .size--all-s'))
  321. .find(el => el.textContent.includes('Reaktionen'));
  322.  
  323. if (reactionsElement) {
  324. const linkElement = document.createElement('span');
  325. linkElement.className = 'showReactionsBtn';
  326. linkElement.textContent = 'anzeigen';
  327. linkElement.style.backgroundColor = '#e6f7e6';
  328. linkElement.style.padding = '0 4px';
  329. linkElement.style.borderRadius = '3px';
  330. linkElement.style.cursor = 'pointer';
  331. linkElement.style.marginLeft = '5px';
  332. linkElement.style.fontSize = '14px';
  333.  
  334. linkElement.onclick = () => {
  335. const p = document.querySelector('.popover--visible');
  336. const username = p?.querySelector('a[href^="/profile/"]')?.getAttribute('href')?.split('/')[2];
  337. if (!username) return;
  338. viewReactions(username);
  339. };
  340.  
  341. reactionsElement.appendChild(document.createTextNode(' '));
  342. reactionsElement.appendChild(linkElement);
  343. }
  344. }, 500);
  345. } catch (error) {
  346. console.error('Fehler:', error);
  347. }
  348. }
  349.  
  350. observer.observe(document.body, {
  351. childList: true,
  352. subtree: true,
  353. attributes: true,
  354. attributeFilter: ['class']
  355. });
  356. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址