X WebUI Cleaner

Hides some UI elements on X and keeps the Following feed in chronological order.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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();
  });

})();