Hides some UI elements on X and keeps the Following feed in chronological order.
// ==UserScript==
// @name X WebUI Cleaner
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Hides some UI elements on X and keeps the Following feed in chronological order.
// @match https://x.com/*
// @match https://twitter.com/*
// @grant GM_addStyle
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
const MENU_OPEN_DELAY = 500; // ms to wait for sort dropdown to render
// ---------------------------------------------------------------------------
// 1. STATIC ELEMENT HIDING
// Injected once as a stylesheet. Persists for the lifetime of the page.
// Selectors use stable attributes (href, aria-label, data-testid) over class names.
// ---------------------------------------------------------------------------
GM_addStyle(`
/* Grok icon */
a[href="/i/grok"][aria-label="Grok"],
/* Creator Studio */
a[href="/i/jf/creators/studio"][aria-label],
/* Premium signup tab */
a[href="/i/premium_sign_up"][data-testid="premium-signup-tab"],
/* Subscribe to Premium aside */
aside[aria-label*="Premium"] {
display: none !important;
pointer-events: none !important;
}
`);
// ---------------------------------------------------------------------------
// 2. DYNAMIC ELEMENT HIDING — run ONCE after the page has settled.
// Targets the Recommended tab and Trending section, which X renders via
// JavaScript after initial load. No MutationObserver needed.
// ---------------------------------------------------------------------------
function hideDynamicElements() {
// --- For You ("추천") tab — always the first child of the tab list ---
const forYouTab = document.querySelector('[role="tablist"]')?.firstElementChild;
if (forYouTab) forYouTab.style.setProperty('display', 'none', 'important');
// --- Entire right sidebar ---
const sidebar = document.querySelector('[data-testid="sidebarColumn"]');
if (sidebar) sidebar.style.setProperty('display', 'none', 'important');
}
// Called once, outside the function, after X's JS has rendered the elements.
setTimeout(hideDynamicElements, 1500);
// ---------------------------------------------------------------------------
// 3. HELPERS
// ---------------------------------------------------------------------------
/** Finds the first element matching `selector` whose text contains `text`. */
function findByText(selector, text) {
return [...document.querySelectorAll(selector)].find(el =>
el.textContent.includes(text)
);
}
/** Clicks an element if it exists. Returns true on success. */
function tryClick(el) {
if (!el) return false;
el.click();
return true;
}
// ---------------------------------------------------------------------------
// 4. FOLLOWING TAB
// The Following tab is uniquely identified by [role="tab"][aria-haspopup="menu"].
// No text-search loop is needed.
// ---------------------------------------------------------------------------
function ensureFollowingTab() {
const followingTab = document.querySelector('[role="tab"][aria-haspopup="menu"]');
if (!followingTab) return false;
if (followingTab.getAttribute('aria-selected') !== 'true') {
followingTab.click();
}
return true;
}
// ---------------------------------------------------------------------------
// 5. CHRONOLOGICAL ORDER
// Opens the sort dropdown, then clicks "최근" only if it isn't already active.
// Note: the sort menu is never pre-open during automated checks, so we must
// open it first, then inspect its state before deciding whether to click.
// ---------------------------------------------------------------------------
function ensureLatestOrder() {
const sortButton = document.querySelector(
'[data-testid="timelineSort"], [aria-label*="타임라인"], [aria-label*="Timeline"]'
);
if (!tryClick(sortButton)) return;
setTimeout(() => {
const latestItem = findByText('[role="menuitem"]', '최근');
if (!latestItem) return;
// A checkmark SVG inside the menuitem means this option is already selected.
if (latestItem.querySelector('svg')) {
// Already in latest mode — dismiss the menu without making a change.
sortButton.click();
} else {
tryClick(latestItem);
}
}, MENU_OPEN_DELAY);
}
// ---------------------------------------------------------------------------
// 6. MAIN CHECK
// ---------------------------------------------------------------------------
function runCheck() {
if (!ensureFollowingTab()) return; // Tab not in DOM yet; skip.
// Brief delay so the tab's feed can render before we touch the sort order.
setTimeout(ensureLatestOrder, MENU_OPEN_DELAY);
}
// ---------------------------------------------------------------------------
// 7. SCHEDULING
// Runs once on load, then every 10 minutes only while this tab is active.
// Also re-runs immediately when the user switches back to this tab.
// ---------------------------------------------------------------------------
window.addEventListener('load', runCheck);
setInterval(() => {
if (document.visibilityState === 'visible') runCheck();
}, INTERVAL_MS);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') runCheck();
});
})();