Greasy Fork is available in English.

Twitter (X) 片思い表示

Twitter (X) で片思いフォロー中のアカウントのみを表示できます。また、両思いフォロー中のアカウントは強調表示できます。

// ==UserScript==
// @name         Twitter (X) 片思い表示
// @namespace    https://kuds.win/
// @version      1.2
// @description  Twitter (X) で片思いフォロー中のアカウントのみを表示できます。また、両思いフォロー中のアカウントは強調表示できます。
// @author       KUDs
// @match        https://twitter.com/*
// @match        https://x.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant        none
// @license      GPL-3.0-or-later
// ==/UserScript==

(function () {
  "use strict";

  // デフォルトは非表示&ハイライト無し
  let isHiding = false;
  let isHighlighting = false;

  // ボタン作成時に共通スタイル適用
  function createStyledButton(text, clickHandler) {
    const button = document.createElement("button");
    button.textContent = text;

    // 共通スタイル
    Object.assign(button.style, {
      border: "none",
      borderRadius: "20px",
      padding: "10px 20px",
      marginTop: "10px",
      cursor: "pointer",
      transition: "filter 0.3s, transform 0.3s",
    });

    button.addEventListener("mouseover", () => {
      Object.assign(button.style, {
        filter: "brightness(1.25)",
        transform: "scale(1.05)",
      });
    });

    button.addEventListener("mouseout", () => {
      Object.assign(button.style, {
        filter: "brightness(1)",
        transform: "scale(1)",
      });
    });

    button.addEventListener("click", clickHandler);
    return button;
  }

  // hide button click ハンドラ
  function handleHideButtonClick() {
    isHiding = !isHiding;
    hideButton.textContent = isHiding ? "片思い ONLY: ON" : "片思い ONLY: OFF";
    hideButton.style.backgroundColor = isHiding ? "skyblue" : "gray";
    updateDOM();
  }

  // highlight button click ハンドラ
  function handleHighlightButtonClick() {
    isHighlighting = !isHighlighting;
    highlightButton.textContent = isHighlighting
      ? "両思い HIGHLIGHT: ON"
      : "両思い HIGHLIGHT: OFF";
    highlightButton.style.backgroundColor = isHighlighting ? "pink" : "gray";
    updateDOM();
  }

  const hideButton = createStyledButton(
    "片思い ONLY: OFF",
    handleHideButtonClick
  );
  hideButton.style.backgroundColor = "gray";

  const highlightButton = createStyledButton(
    "両思い HIGHLIGHT: OFF",
    handleHighlightButtonClick
  );
  highlightButton.style.backgroundColor = "gray";
  highlightButton.style.display = "none";

  // ボタン配置の初期化
  function initializeButtons() {
    const targetNavElement = document.querySelector(
      '[data-testid="SideNav_NewTweet_Button"]'
    );
    if (targetNavElement) {
      const parentNavElement = targetNavElement.parentNode;
      if (parentNavElement && !hideButton.parentNode) {
        parentNavElement.appendChild(hideButton);
        parentNavElement.appendChild(highlightButton);
      }
    }
  }

  // cellInnerDivの表示/非表示の更新
  function updateCellInnerDivVisibility(node) {
    if (
      node.textContent.includes("フォローされています") ||
      node.textContent.includes("両思いです♡")
    ) {
      node.style.display = isHiding ? "none" : "";
    }
  }

  // userFollowIndicatorの表示の更新
  function updateFollowIndicator(node) {
    if (
      node.textContent.trim() === "フォローされています" ||
      node.textContent.trim() === "両思いです♡"
    ) {
      const spanElement = node.querySelector("span");
      if (isHighlighting) {
        node.style.backgroundColor = "pink";
        spanElement.textContent = "両思いです♡";
      } else {
        node.style.backgroundColor = "";
        spanElement.textContent = "フォローされています";
      }
    }
  }

  // DOMのアップデート
  function updateDOM(mutationsList = []) {
    // 既存のDOMノードに対する更新を追加
    const cellInnerDivs = document.querySelectorAll(
      'div[data-testid="cellInnerDiv"]'
    );
    cellInnerDivs.forEach(updateCellInnerDivVisibility);

    const userFollowIndicators = document.querySelectorAll(
      '[data-testid="userFollowIndicator"]'
    );
    userFollowIndicators.forEach(updateFollowIndicator);

    for (let mutation of mutationsList) {
      if (mutation.addedNodes && mutation.addedNodes.length > 0) {
        mutation.addedNodes.forEach((node) => {
          // SideNavへのボタン追加
          if (
            node.matches &&
            node.matches('[data-testid="SideNav_NewTweet_Button"]')
          ) {
            initializeButtons();
          }

          // cellInnerDivとuserFollowIndicatorの処理
          if (node.matches && node.matches('div[data-testid="cellInnerDiv"]')) {
            updateCellInnerDivVisibility(node);
          }
          if (
            node.matches &&
            node.matches('[data-testid="userFollowIndicator"]')
          ) {
            updateFollowIndicator(node);
          }
        });
      }
    }

    // highlightButtonの表示/非表示
    highlightButton.style.display = isHiding ? "none" : "";

    // ボタン配置を再確認
    initializeButtons();
  }

  const observer = new MutationObserver(updateDOM);
  observer.observe(document.body, {
    childList: true,
    subtree: true,
  });

  // スクリプトの最後で初期化関数を実行
  initializeButtons();
})();