您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display the number of followers for Twitter accounts
// ==UserScript== // @name Twitter Follower Count // @namespace amm1rr.com.twitter.follower.count // @version 0.3.2 // @homepage https://github.com/Amm1rr/Twitter-Follower-Count/ // @description Display the number of followers for Twitter accounts // @author Mohammad Khani (@m_khani65) // @match https://x.com/* // @match https://twitter.com/* // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; /** * Cache to store user data to prevent redundant processing. * Key: screen_name, Value: user details object. * @type {Map<string, Object>} */ const userCache = new Map(); /** * Store reference to the original XMLHttpRequest.send method. */ const originalSend = XMLHttpRequest.prototype.send; /** * Override XMLHttpRequest.send to intercept API responses. * Filters for responses from Twitter API endpoints and extracts user data, * caching the data if it's not already present. * * @param {...any} args - Arguments passed to the original send method. */ XMLHttpRequest.prototype.send = function (...args) { this.addEventListener("load", () => { // Process only API responses relevant to Twitter data. if (!this.responseURL || !this.responseURL.includes("/i/api/")) return; let responseData; try { responseData = decodeResponse(this); if (!responseData) return; } catch (e) { console.error("Failed to decode response:", e); return; } try { const responseJSON = JSON.parse(responseData); const users = extractUsers(responseJSON, "screen_name"); users.forEach((user) => { if (!user.screen_name || !user.followers_count) return; // Cache the user data if not already present. if (!userCache.has(user.screen_name)) { cacheUserData(user); } }); } catch (e) { // Fail silently if JSON parsing fails. } }); originalSend.apply(this, args); }; /** * Decodes the XMLHttpRequest response based on its type. * * @param {XMLHttpRequest} xhr - The XMLHttpRequest object. * @returns {string|null} The decoded response data or null if decoding fails. */ const decodeResponse = (xhr) => { if (xhr.responseType === "" || xhr.responseType === "text") { return xhr.responseText; } else if (xhr.responseType === "arraybuffer") { return new TextDecoder("utf-8").decode(xhr.response); } return null; }; /** * Caches user data. * * @param {Object} user - The user data object from the API. */ const cacheUserData = (user) => { userCache.set(user.screen_name, { name: user.name, screen_name: user.screen_name, followers_count: user.followers_count, formatted_followers_count: formatFollowers(user.followers_count), friends_count: user.friends_count, }); }; /** * Recursively traverse an object to extract all sub-objects containing a specific key. * * @param {Object} obj - The object to traverse. * @param {string} key - The key to search for (e.g., "screen_name"). * @param {Array<Object>} [result=[]] - Array to accumulate found objects. * @returns {Array<Object>} Array of objects that contain the specified key. */ const extractUsers = (obj, key, result = []) => { for (const value of Object.values(obj)) { if (value && typeof value === "object") { if (value.hasOwnProperty(key)) { result.push(value); } extractUsers(value, key, result); } } return result; }; /** * Format a number into a human-readable string with K/M suffix. * * @param {number} number - The number of followers. * @returns {string} Formatted number string. */ const formatFollowers = (number) => { if (number >= 1000000) return `${(number / 1000000).toFixed(1)}M`; if (number >= 1000) return `${(number / 1000).toFixed(1)}K`; return number.toString(); }; /** * Create a DOM element (span) to display the formatted follower count. * * @param {string} formattedCount - The formatted follower count string. * @returns {HTMLElement} The created span element. */ const createFollowerCountElement = (formattedCount) => { const span = document.createElement("span"); span.className = "count-follower"; span.innerText = formattedCount; Object.assign(span.style, { position: "absolute", bottom: "-2px", left: "50%", transform: "translate(-50%)", fontSize: "8px", fontWeight: "bold", color: "#ffffff", backgroundColor: "rgb(29, 155, 240)", border: "1px solid #0867d2", borderRadius: "9999px", padding: "0px 4px", whiteSpace: "nowrap", }); return span; }; /** * Update the DOM to display follower counts for all cached users. * It searches for profile links in the document and appends the follower count * element next to the profile image. */ const updateFollowerCounts = () => { userCache.forEach((user, screen_name) => { const profileLinks = document.querySelectorAll( `a[href*="/${screen_name}"]` // Modified selector to be more robust ); profileLinks.forEach((link) => { // Skip if follower count is already appended. if (link.querySelector(".count-follower")) return; const parent = link.parentNode; if (!parent) return; const img = parent.querySelector('img[draggable="true"]'); if (!img) return; // Adjust styles for proper display. parent.style.overflow = "inherit"; parent.style.clipPath = "none"; const closestUl = parent.closest("ul"); if (closestUl) { closestUl.style.overflow = "inherit"; } const span = createFollowerCountElement(user.formatted_followers_count); link.appendChild(span); }); }); }; /** * Debounce function to limit the rate at which a function is executed. * * @param {Function} func - The function to debounce. * @param {number} delay - The delay in milliseconds. * @returns {Function} A debounced version of the given function. */ const debounce = (func, delay) => { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); }; }; // Debounced version of updateFollowerCounts to reduce excessive calls. const debouncedUpdateFollowerCounts = debounce(updateFollowerCounts, 100); // Update follower counts on initial page load and during scroll events. window.addEventListener("load", debouncedUpdateFollowerCounts); document.addEventListener("scroll", debouncedUpdateFollowerCounts); /** * Use MutationObserver to monitor changes in the DOM. * This helps in detecting dynamically added elements (e.g., new user profiles) * and triggers an update to append follower counts accordingly. */ const observer = new MutationObserver(debouncedUpdateFollowerCounts); observer.observe(document.body, { childList: true, subtree: true }); })();