Hide For You (schedule)

Hides the 'For You' tab and auto-switches to 'Following' on X.com during configured time windows

// ==UserScript==
// @name         Hide For You (schedule)
// @namespace    f_d
// @version      1.2
// @description  Hides the 'For You' tab and auto-switches to 'Following' on X.com during configured time windows
// @author       GPT-5
// @match        https://x.com/*
// @match        https://twitter.com/*
// @icon         https://abs.twimg.com/favicons/twitter.ico
// @grant        none
// ==/UserScript==

/*** ──────────────────────────────────────────────────────────────
 * SCHEDULE CONFIG
 *
 * Time is local browser time, 24h "HH:MM".
 * Days: 0=Sun, 1=Mon, ..., 6=Sat
 * The filter is ACTIVE if the current time falls into ANY range
 * listed for the current day. "default" applies when a day has no
 * explicit entry. Overnight ranges are supported (e.g. 22:00–03:00).
 *
 * My Settings:
 *   - Mon–Fri: block active all day
 *   - Sat & Sun: active until 16:00
 * ───────────────────────────────────────────────────────────────*/
const SCHEDULE = {
  // Sunday
  0: [{ start: "00:00", end: "16:00" }],
  // Monday–Friday
  1: [{ start: "00:00", end: "24:00" }],
  2: [{ start: "00:00", end: "24:00" }],
  3: [{ start: "00:00", end: "24:00" }],
  4: [{ start: "00:00", end: "24:00" }],
  5: [{ start: "00:00", end: "24:00" }],
  // Saturday
  6: [{ start: "00:00", end: "16:00" }],
  // Fallback if a day is not listed
  default: [{ start: "00:00", end: "24:00" }],
};
/*** ─────────────────────────────────────────────────────────── ***/

(function () {
  'use strict';

  // Match both English and German labels, fall back to exact text match
  const LABELS_FOR_YOU = new Set(["For you", "Für dich"]);
  const LABELS_FOLLOWING = new Set(["Following", "Folge ich"]);

  const HIDDEN_ATTR = "data-hidden-by-userscript-for-you";

  function timeToMinutes(t) {
    const [h, m] = t.split(":").map(Number);
    return h * 60 + (m || 0);
  }

  function inRange(nowMin, startMin, endMin) {
    // Handles normal and overnight (wrap-around) ranges
    if (startMin === endMin) return false; // empty range
    if (startMin < endMin) return nowMin >= startMin && nowMin < endMin;
    return nowMin >= startMin || nowMin < endMin;
  }

  function isFeatureActiveNow() {
    const now = new Date();
    const day = now.getDay(); // 0..6
    const minutes = now.getHours() * 60 + now.getMinutes();
    const ranges = SCHEDULE.hasOwnProperty(day) ? SCHEDULE[day] : SCHEDULE.default;

    if (!ranges || ranges.length === 0) return false;
    return ranges.some(({ start, end }) => inRange(minutes, timeToMinutes(start), timeToMinutes(end)));
  }

  function getTabs() {
    const tabs = Array.from(document.querySelectorAll('[role="tab"]'));
    let forYouTab = null, followingTab = null;

    tabs.forEach(tab => {
      const text = (tab.textContent || "").trim();
      if (LABELS_FOR_YOU.has(text)) forYouTab = tab;
      if (LABELS_FOLLOWING.has(text)) followingTab = tab;
    });

    return { forYouTab, followingTab };
  }

  function hideForYouAndSwitch() {
    const { forYouTab, followingTab } = getTabs();
    if (!forYouTab) return;

    // Hide the "For you" tab
    if (forYouTab.style.display !== "none") {
      forYouTab.style.display = "none";
      forYouTab.setAttribute(HIDDEN_ATTR, "true");
    }

    // If "For you" was active, switch to "Following"
    if (forYouTab.getAttribute('aria-selected') === 'true' && followingTab) {
      // Some builds require a mouse event to trigger navigation properly
      followingTab.click();
    }
  }

  function unhideForYou() {
    const { forYouTab } = getTabs();
    if (!forYouTab) return;
    if (forYouTab.getAttribute(HIDDEN_ATTR) === "true") {
      forYouTab.style.display = "";
      forYouTab.removeAttribute(HIDDEN_ATTR);
    }
  }

  function applyBehavior() {
    if (isFeatureActiveNow()) {
      hideForYouAndSwitch();
    } else {
      unhideForYou();
    }
  }

  // Run immediately
  applyBehavior();

  // Observe DOM changes (tabs re-render frequently)
  const observer = new MutationObserver(() => applyBehavior());
  observer.observe(document.body, { childList: true, subtree: true });

  // Re-check schedule every minute in case the hour changes without DOM mutations
  const intervalId = setInterval(applyBehavior, 60 * 1000);

  // Cleanup on unload
  window.addEventListener('unload', () => {
    observer.disconnect();
    clearInterval(intervalId);
  }, false);
})();