Discourse Avatar Replacer (DiceBear Adventurer) v1.2 (Debug Enabled)

Replaces Discourse user avatars with DiceBear Adventurer SVGs based on user ID or username. (Debug logging enabled)

  1. // ==UserScript==
  2. // @name Discourse Avatar Replacer (DiceBear Adventurer) v1.2 (Debug Enabled)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Replaces Discourse user avatars with DiceBear Adventurer SVGs based on user ID or username. (Debug logging enabled)
  6. // @author dari & AI Assistant
  7. // @match *://*.discourse.org/*
  8. // @match *://*.linux.do/*
  9. // @icon https://api.dicebear.com/9.x/adventurer/svg?seed=tampermonkey&size=64
  10. // @grant none
  11. // @run-at document-idle
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. console.log('[DICEBEAR REPLACER] Script starting...'); // Log script start
  19.  
  20. const DICEBEAR_API_URL_BASE = 'https://api.dicebear.com/9.x/adventurer/svg?seed=';
  21. const PROCESSED_ATTRIBUTE = 'data-dicebear-avatar-replaced';
  22.  
  23. function getUserIdentifier(imgElement) {
  24. console.log('[DICEBEAR REPLACER] Trying to get identifier for:', imgElement);
  25.  
  26. // 1. Check data-user-id on img
  27. let userId = imgElement.getAttribute('data-user-id');
  28. if (userId && userId.trim() !== '') {
  29. console.log(`[DICEBEAR REPLACER] Found User ID on img: ${userId}`);
  30. return userId;
  31. }
  32.  
  33. // 2. Check data-user-id on closest ancestor
  34. const userElement = imgElement.closest('[data-user-id]');
  35. if (userElement) {
  36. userId = userElement.getAttribute('data-user-id');
  37. if (userId && userId.trim() !== '') {
  38. console.log(`[DICEBEAR REPLACER] Found User ID on ancestor [${userElement.tagName}]: ${userId}`);
  39. return userId;
  40. }
  41. }
  42.  
  43. // 3. Fallback: Extract username from parent link href
  44. const parentLink = imgElement.closest('a[href*="/u/"]');
  45. if (parentLink && parentLink.href) {
  46. const match = parentLink.href.match(/\/u\/([^\/]+)/);
  47. if (match && match[1]) {
  48. const username = match[1];
  49. console.log(`[DICEBEAR REPLACER] Found Username in link [${parentLink.tagName}]: ${username}`);
  50. return username; // Use username as the seed
  51. } else {
  52. console.log('[DICEBEAR REPLACER] Found parent link, but no username match in href:', parentLink.href);
  53. }
  54. } else {
  55. console.log('[DICEBEAR REPLACER] No parent link with /u/ found.');
  56. }
  57.  
  58. // 4. Fallback: Username from title/alt (often less reliable)
  59. const usernameFromAttr = imgElement.getAttribute('title') || imgElement.getAttribute('alt');
  60. if (usernameFromAttr && usernameFromAttr.trim() !== '' && !usernameFromAttr.includes('Avatar')) { // Avoid generic "Avatar" alt text
  61. console.log(`[DICEBEAR REPLACER] Found identifier from title/alt: ${usernameFromAttr.trim()}`);
  62. return usernameFromAttr.trim();
  63. }
  64.  
  65.  
  66. console.warn('[DICEBEAR REPLACER] Could not determine User ID or Username for:', imgElement);
  67. return null;
  68. }
  69.  
  70. function replaceAvatars() {
  71. console.log('[DICEBEAR REPLACER] Running replaceAvatars function...');
  72. // Select all images with 'avatar' class NOT already processed
  73. const avatarImages = document.querySelectorAll(`img.avatar:not([${PROCESSED_ATTRIBUTE}])`);
  74. console.log(`[DICEBEAR REPLACER] Found ${avatarImages.length} potential avatar images with class 'avatar' to process.`);
  75.  
  76. if (avatarImages.length === 0) {
  77. console.log("[DICEBEAR REPLACER] No new images with class 'avatar' found this time.");
  78. // Let's also check if ANY images matching the original src pattern exist, maybe the class is wrong on homepage?
  79. // This is for debugging only:
  80. const allUserImages = document.querySelectorAll('img[src*="/user_avatar/"]');
  81. console.log(`[DICEBEAR REPLACER] DEBUG: Found ${allUserImages.length} images with '/user_avatar/' in src (regardless of class).`);
  82. }
  83.  
  84.  
  85. avatarImages.forEach((img, index) => {
  86. console.log(`[DICEBEAR REPLACER] Processing image #${index + 1}:`, img);
  87. img.setAttribute(PROCESSED_ATTRIBUTE, 'true'); // Mark as processed
  88.  
  89. const identifier = getUserIdentifier(img);
  90.  
  91. if (identifier && identifier.trim() !== '') {
  92. const seed = identifier.trim();
  93. const newSrc = `${DICEBEAR_API_URL_BASE}${encodeURIComponent(seed)}`;
  94.  
  95. if (img.src !== newSrc) {
  96. console.log(`[DICEBEAR REPLACER] Replacing src for Identifier: ${seed}. Old src: ${img.src}, New src: ${newSrc}`);
  97. img.src = newSrc; // Replace the source
  98. img.removeAttribute('srcset'); // Remove srcset
  99. // Let's keep width/height for now
  100. } else {
  101. console.log(`[DICEBEAR REPLACER] Identifier ${seed} found, but src ${img.src} is already the target DiceBear URL. Skipping.`);
  102. }
  103. } else {
  104. console.warn('[DICEBEAR REPLACER] No identifier found for image:', img);
  105. }
  106. });
  107. console.log('[DICEBEAR REPLACER] Finished replaceAvatars function run.');
  108. }
  109.  
  110. // --- MutationObserver ---
  111. const observer = new MutationObserver(mutations => {
  112. let needsUpdate = false;
  113. for (const mutation of mutations) {
  114. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  115. for (const node of mutation.addedNodes) {
  116. if (node.nodeType === Node.ELEMENT_NODE) {
  117. if (node.matches(`img.avatar:not([${PROCESSED_ATTRIBUTE}])`) || node.querySelector(`img.avatar:not([${PROCESSED_ATTRIBUTE}])`)) {
  118. console.log('[DICEBEAR REPLACER] MutationObserver detected added node potentially containing an avatar:', node);
  119. needsUpdate = true;
  120. break;
  121. }
  122. }
  123. }
  124. }
  125. if (needsUpdate) break;
  126. }
  127.  
  128. if (needsUpdate) {
  129. console.log('[DICEBEAR REPLACER] DOM change detected, scheduling avatar replacement.');
  130. clearTimeout(observer.debounceTimer);
  131. observer.debounceTimer = setTimeout(replaceAvatars, 200); // Slightly longer debounce for dynamic loads
  132. }
  133. });
  134.  
  135. const config = { childList: true, subtree: true };
  136. observer.observe(document.body, config);
  137. console.log('[DICEBEAR REPLACER] MutationObserver started.');
  138.  
  139. // --- Initial Run ---
  140. console.log('[DICEBEAR REPLACER] Scheduling initial run.');
  141. // Increased timeout significantly to wait for potentially slow homepage elements
  142. setTimeout(replaceAvatars, 1500);
  143.  
  144. })();

QingJ © 2025

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