在 S1 论坛头像右上角显示按钮,点击后替换为 Dicebear 图像。
// ==UserScript==
// @name S1头像替换
// @namespace https://bbs.saraba1st.com
// @version 1.4
// @description 在 S1 论坛头像右上角显示按钮,点击后替换为 Dicebear 图像。
// @author hexie
// @match https://*.saraba1st.com/2b/*
// @match https://stage1st.com/2b/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=saraba1st.com
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Dicebear API 配置
const DICEBEAR_API_URL = 'https://api.dicebear.com/9.x/thumbs/svg';
/**
* 从用户链接中提取 uid。
*/
function extractUidFromHref(href) {
if (!href) return null;
const match = href.match(/uid-(\d+)\.html/);
return match ? match[1] : null;
}
/**
* 替换指定 uid 的头像。
*/
function replaceAvatar(uid) {
if (!uid) return;
const seed = uid;
const newSrc = `${DICEBEAR_API_URL}?seed=${seed}`;
document.querySelectorAll(`img[data-uid="${uid}"]`).forEach((imgElement) => {
if (!imgElement.hasAttribute('data-original-src')) {
imgElement.setAttribute('data-original-src', imgElement.dataset.original || imgElement.src);
}
imgElement.src = newSrc;
});
GM_setValue(uid, newSrc);
// console.log(`[S1 Avatar Replacer] Replaced avatar for UID: ${uid}`);
}
/**
* 恢复指定 uid 的头像为原始头像。
*/
function resetAvatar(uid) {
if (!uid) return;
GM_deleteValue(uid);
document.querySelectorAll(`img[data-uid="${uid}"]`).forEach((imgElement) => {
const originalSrc = imgElement.getAttribute('data-original-src');
if (originalSrc) {
imgElement.src = originalSrc;
}
});
// console.log(`[S1 Avatar Replacer] Reset avatar for UID: ${uid}`);
}
/**
* 为头像元素添加替换按钮。
*/
function addReplaceButton(avatarContainer, imgElement, uid) {
const button = document.createElement('button');
button.textContent = '㔢';
button.title = '替换为 Dicebear 头像';
// --- 按钮样式 ---
button.style.position = 'absolute';
button.style.top = '2px';
button.style.right = '2px';
button.style.zIndex = '1001';
button.style.cursor = 'pointer';
button.style.background = 'rgba(0, 123, 255, 0.8)';
button.style.color = '#ffffff';
button.style.border = '1px solid rgba(255, 255, 255, 0.5)';
button.style.borderRadius = '50%';
button.style.width = '18px';
button.style.height = '18px';
button.style.fontSize = '10px';
button.style.lineHeight = '16px';
button.style.textAlign = 'center';
button.style.padding = '0';
button.style.display = 'none';
button.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.3)';
button.style.transition = 'background-color 0.2s ease, opacity 0.2s ease';
button.style.opacity = '0.8';
// --- 按钮交互 ---
avatarContainer.style.position = 'relative';
avatarContainer.addEventListener('mouseover', () => {
button.style.display = 'block';
});
avatarContainer.addEventListener('mouseout', () => {
button.style.display = 'none';
});
button.addEventListener('mouseover', () => {
button.style.background = 'rgba(0, 100, 210, 0.9)';
button.style.opacity = '1';
});
button.addEventListener('mouseout', () => {
button.style.background = 'rgba(0, 123, 255, 0.8)';
button.style.opacity = '0.8';
});
button.onclick = function(event) {
event.preventDefault();
event.stopPropagation();
replaceAvatar(uid);
};
avatarContainer.appendChild(button);
}
/**
* 初始化页面上的所有头像。
*/
function initializeAvatars() {
document.querySelectorAll('div.avatar, div.icn.avt').forEach(function(avatarContainer) {
const imgElement = avatarContainer.querySelector('img');
const linkElement = avatarContainer.querySelector('a[href*="uid-"]');
if (!imgElement) return;
const uid = linkElement ? extractUidFromHref(linkElement.href) : null;
if (uid) {
imgElement.setAttribute('data-uid', uid);
if (!imgElement.hasAttribute('data-original-src')) {
const originalSrc = imgElement.dataset.original || imgElement.src;
imgElement.setAttribute('data-original-src', originalSrc);
}
const savedSrc = GM_getValue(uid);
if (savedSrc) {
imgElement.src = savedSrc;
} else {
imgElement.src = imgElement.getAttribute('data-original-src');
}
addReplaceButton(avatarContainer, imgElement, uid);
} else {
if (!imgElement.hasAttribute('data-original-src')) {
const originalSrc = imgElement.dataset.original || imgElement.src;
imgElement.setAttribute('data-original-src', originalSrc);
imgElement.src = originalSrc;
}
}
});
}
// --- Greasemonkey 菜单命令 ---
// 清除所有保存的头像数据并恢复原始头像
GM_registerMenuCommand("清除所有S1替换头像数据", function() {
let clearedCount = 0;
document.querySelectorAll('img[data-uid]').forEach(function(imgElement) {
const uid = imgElement.getAttribute('data-uid');
if (uid && GM_getValue(uid)) {
resetAvatar(uid);
clearedCount++;
} else if (uid) {
const originalSrc = imgElement.getAttribute('data-original-src');
if (originalSrc && imgElement.src !== originalSrc) {
imgElement.src = originalSrc;
}
}
});
if (clearedCount > 0) {
alert(`已清除 ${clearedCount} 个用户的替换头像数据并恢复原始头像。`);
} else {
alert("没有找到已保存的替换头像数据。");
}
});
// --- 初始化脚本 ---
initializeAvatars();
// 使用 MutationObserver 监听 DOM 变化,处理动态加载的头像
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.addedNodes.length) {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches('div.avatar, div.icn.avt') || node.querySelector('div.avatar, div.icn.avt')) {
// 简单的重新扫描整个文档,确保处理新加载的头像
// 对于大型或频繁更新的页面,可以优化为只处理新添加的节点
initializeAvatars();
return;
}
}
});
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();