您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show avatars + relative timestamps in like list
当前为
// ==UserScript== // @name PlatesMania Like Avatars + Relative Time // @namespace pm-like-avatars // @version 1.1 // @description Show avatars + relative timestamps in like list // @match https://platesmania.com/userXXXXXX // @run-at document-idle // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @license MIT // ==/UserScript== (function () { "use strict"; const CACHE_KEY = "pm_profile_pic_cache_v1"; const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000; GM_addStyle(` .pm-like-avatar { width: 20px !important; height: 20px !important; border-radius: 4px !important; /* <-- change this */ object-fit: cover !important; vertical-align: text-bottom !important; margin-right: 6px !important; overflow: hidden !important; } `); // ===== Cache stuff ===== function readCache() { try { const raw = typeof GM_getValue === "function" ? GM_getValue(CACHE_KEY, "{}") : localStorage.getItem(CACHE_KEY) || "{}"; return JSON.parse(raw); } catch { return {}; } } function writeCache(obj) { const raw = JSON.stringify(obj); if (typeof GM_setValue === "function") GM_setValue(CACHE_KEY, raw); else localStorage.setItem(CACHE_KEY, raw); } function getFromCache(userId) { const cache = readCache(); const entry = cache[userId]; if (!entry) return null; if (Date.now() - entry.ts > MAX_AGE_MS) { delete cache[userId]; writeCache(cache); return null; } return entry.url; } function putInCache(userId, url) { const cache = readCache(); cache[userId] = { url, ts: Date.now() }; writeCache(cache); } // ===== Avatar processing ===== async function fetchProfileAvatar(userPath) { const res = await fetch(new URL(userPath, location.origin), { credentials: "same-origin" }); if (!res.ok) throw new Error(res.status); const html = await res.text(); const doc = new DOMParser().parseFromString(html, "text/html"); const img = doc.querySelector(".profile-img[src]"); return img ? new URL(img.getAttribute("src"), location.origin).toString() : null; } function addAvatarToRow(rowEl, avatarUrl) { if (!rowEl || rowEl.dataset.pmAvatarAdded === "1") return; const strong = rowEl.querySelector("strong"); if (!strong) return; const userLink = strong.querySelector('a[href^="/user"]'); if (!userLink) return; if (strong.querySelector("img.pm-like-avatar")) { rowEl.dataset.pmAvatarAdded = "1"; return; } const img = document.createElement("img"); img.className = "pm-like-avatar"; img.src = avatarUrl; strong.insertBefore(img, userLink); rowEl.dataset.pmAvatarAdded = "1"; } async function processRow(rowEl) { if (rowEl.dataset.pmAvatarAdded === "1") return; const userLink = rowEl.querySelector('strong > a[href^="/user"]'); if (!userLink) return; const userHref = userLink.getAttribute("href"); const userId = (userHref.match(/user(\d+)/) || [])[1]; if (!userId) return; const cached = getFromCache(userId); if (cached) { addAvatarToRow(rowEl, cached); return; } try { const url = await fetchProfileAvatar(userHref); if (url) { putInCache(userId, url); addAvatarToRow(rowEl, url); } } catch {} } function processAllAvatars(root = document) { root.querySelectorAll(".col-xs-12.margin-bottom-5.bg-info").forEach(processRow); } // ===== Relative time ===== function formatRelative(date) { const diff = (Date.now() - date.getTime()) / 1000; if (diff < 60) return `${Math.floor(diff)}s ago`; if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; if (diff < 30 * 86400) return `${Math.floor(diff / 86400)}d ago`; return date.toLocaleDateString(); } function parseMoscowTime(str) { // format: YYYY-MM-DD HH:mm:ss (Moscow time, UTC+3 permanent) const [datePart, timePart] = str.split(" "); const [y, m, d] = datePart.split("-").map(Number); const [hh, mm, ss] = timePart.split(":").map(Number); // create as if it's UTC, then shift from UTC+3 const utcMs = Date.UTC(y, m - 1, d, hh - 3, mm, ss); return new Date(utcMs); } function processAllTimes(root = document) { root.querySelectorAll(".col-xs-12.margin-bottom-5.bg-info small").forEach((el) => { if (el.dataset.pmTimeDone === "1") return; const txt = el.textContent.trim(); if (!/^\d{4}-\d{2}-\d{2}/.test(txt)) return; const date = parseMoscowTime(txt); el.textContent = formatRelative(date); el.dataset.pmTimeDone = "1"; }); } // ===== Observe dynamic changes ===== function setupObserver() { const container = document.querySelector("#mCSB_2_container, #content") || document.body; const obs = new MutationObserver((mutations) => { mutations.forEach((m) => { m.addedNodes.forEach((n) => { if (!(n instanceof HTMLElement)) return; processAllAvatars(n); processAllTimes(n); }); }); }); obs.observe(container, { childList: true, subtree: true }); } // Kick off processAllAvatars(document); processAllTimes(document); setupObserver(); setInterval(() => { processAllAvatars(document); processAllTimes(document); }, 1500); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址