// ==UserScript== // @name AttachHowOldtoUserinPosts // @namespace https://jirehlov.com // @version 1.14 // @description Show how old a user is in posts // @author Jirehlov // @match https://bgm.tv/* // @match https://chii.in/* // @match https://bangumi.tv/* // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function () { const delay = 4000; const ageColors = [ { threshold: 2, color: "#FFC966" }, { threshold: 5, color: "#FFA500" }, { threshold: 10, color: "#F09199" }, { threshold: Infinity, color: "#FF0000" } ]; let requestCount = 0; let usersToFetch = []; const sortedUserIds = Object.keys(localStorage).filter(key => key.startsWith("userAge_")).map(key => key.replace("userAge_", "")).filter(value => Number.isInteger(parseInt(value, 10))).map(value => parseInt(value, 10)).sort((a, b) => a - b); function calculateAge(birthDate) { const today = new Date(); const dob = new Date(birthDate); let age = today.getFullYear() - dob.getFullYear(); const monthDiff = today.getMonth() - dob.getMonth(); if (monthDiff < 0 || monthDiff === 0 && today.getDate() < dob.getDate()) { age--; } return age; } function findClosestMatchingDates(userId) { let lower = -1, upper = -1; for (let i = 0; i < sortedUserIds.length; i++) { if (sortedUserIds[i] < userId) { lower = sortedUserIds[i]; } if (sortedUserIds[i] > userId) { upper = sortedUserIds[i]; break; } } if (lower !== -1 && upper !== -1) { let lowerDate = localStorage.getItem("userAge_" + lower); let upperDate = localStorage.getItem("userAge_" + upper); if (lowerDate === upperDate) { return lowerDate; } } return null; } function fetchAndStoreUserAge(userLink, delayTime) { setTimeout(() => { $.ajax({ url: userLink, dataType: "html", success: function (data) { const parser = new DOMParser(); const doc = parser.parseFromString(data, "text/html"); let registrationDate = $(doc).find("ul.network_service li:first span.tip").text().replace(/加入/g, "").trim(); if (registrationDate) { const userId = userLink.split("/").pop(); localStorage.setItem("userAge_" + userId, registrationDate); displayUserAge(userId, registrationDate); } } }); }, delayTime); } function displayUserAge(userId, registrationDate) { const userAnchor = $("strong a.l[href$='/user/" + userId + "']"); if (userAnchor.length > 0 && userAnchor.next(".age-badge").length === 0) { const userAge = calculateAge(registrationDate); if (!isNaN(userAge)) { let badgeColor = ageColors.find(color => userAge <= color.threshold).color; const badge = ` ${ userAge }年`; userAnchor.after($(badge)); } } } function importUserAges() { const input = document.createElement("input"); input.type = "file"; input.accept = "application/json"; input.onchange = function (event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function (e) { try { const userAges = JSON.parse(e.target.result); Object.keys(userAges).forEach(key => localStorage.setItem(key, userAges[key])); alert("导入成功"); } catch (error) { alert("无效的JSON文件\uFF1A", error); } }; reader.readAsText(file); } }; input.click(); } function exportUserAges() { const userAges = {}; Object.keys(localStorage).forEach(key => { if (key.startsWith("userAge_")) { userAges[key] = localStorage.getItem(key); } }); const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const entryCount = Object.keys(userAges).length; const filename = `userAges_${ timestamp }_${ entryCount }entries.json`; const blob = new Blob([JSON.stringify(userAges, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } const badgeUserPanel = document.querySelector("ul#badgeUserPanel"); if (badgeUserPanel) { const importButton = document.createElement("a"); importButton.href = "#"; importButton.textContent = "导入用户生日数据"; importButton.onclick = event => { event.preventDefault(); importUserAges(); }; const exportButton = document.createElement("a"); exportButton.href = "#"; exportButton.textContent = "导出用户生日数据"; exportButton.onclick = event => { event.preventDefault(); exportUserAges(); }; const listItem = document.createElement("li"); listItem.appendChild(importButton); badgeUserPanel.appendChild(listItem); const exportListItem = document.createElement("li"); exportListItem.appendChild(exportButton); badgeUserPanel.appendChild(exportListItem); } $("strong a.l:not(.avatar)").each(function () { const userLink = $(this).attr("href"); const userId = userLink.split("/").pop(); const avatarMatch = $(this).closest("div.inner").prev("a.avatar").find("span.avatarNeue").attr("style")?.match(/\/(\d+)\.jpg/); let realUserId = avatarMatch ? avatarMatch[1] : userId; let storedDate = localStorage.getItem("userAge_" + userId); if (!storedDate) { if (!isNaN(userId)) { storedDate = findClosestMatchingDates(userId); if (storedDate) { console.log(`${ userId } ${ storedDate }`); } } else if (!isNaN(realUserId)) { storedDate = findClosestMatchingDates(realUserId); if (storedDate) { console.log(`${ userId } ${ realUserId } ${ storedDate }`); } } } if (storedDate) { localStorage.setItem("userAge_" + userId, storedDate); displayUserAge(userId, storedDate); } else { usersToFetch.push(userLink); } }); usersToFetch = [...new Set(usersToFetch)]; console.log("Users to fetch:", usersToFetch); usersToFetch.forEach((userLink, index) => { fetchAndStoreUserAge(userLink, index * delay); }); const jsonURL = "https://jirehlov.com/userages.json"; function fetchUserAgesOnline() { const lastFetchTime = localStorage.getItem("lastFetchedUserAges"); const now = Date.now(); if (!lastFetchTime || now - parseInt(lastFetchTime, 10) > 7 * 24 * 60 * 60 * 1000) { fetch(jsonURL).then(response => response.json()).then(data => { Object.keys(data).forEach(key => { localStorage.setItem(key, data[key]); }); localStorage.setItem("lastFetchedUserAges", now); }).catch(error => console.error("Failed to fetch user ages:", error)); } } fetchUserAgesOnline(); }());